在终端执行命令时,Linux会建立进程,程序执行完,进程会被终止;Linux是一个多任务的OS,允许多个进程并发运行;
Linxu中启动进程的两种途径:
①手动启动(前台进程(命令gedit)...后台进程(命令+‘&’))
②调度启动 --at/cron启动
at执行的命令一般不会在终端回显,可以采用重定向到文件就可以查看;
Linux环境下常见的进程操作命令:
ps--查看系统中的进程;
top--动态显示系统中的进程;
kil--终止进程(包括后台进程);
Linux环境下的进程会有一个唯一的进程标识符(pid),进程标识有进程号PID和它的父进程号PPID,PID唯一标识一个进程,其中,PID和PPID都是非零正整数。获得当前进程的PID和PPID的系统调用是getpid()和getppid()函数【头文件unistd.h】;
ps -ef|grep test1 (可以查找程序test1.c运行时的进程号);
3个特殊进程:
PID为0的调度进程,是内核的一部分,也被称为交换进程和系统进程;
PID为1的init进程(centos6及之前版本),init进程是内核启动并运行的第一个用户进程(不是内核中的系统进程),负责对系统进行初始化,并将系统引导到某个状态。init进程不能被终止。
PID为2的kthreadd内核进程,其是一个内核线程,负责执行后台操作。
进程相关函数:
exec函数族--在进程中启动另一个程序执行;
system--在进程中开始另一个进程;
fork--从已经存在的进程中复制一个新进程
sleep--让进程暂停运行一段时间;
exit/_exit--用来终止进程
wait/waitpid--暂停父进程,等待子进程完成运行;
Fork函数
进程调用一次fork函数创建一个新进程,新创建的新进程被称为子进程。该函数调用一次但是返回两次,子进程返回值是0,父进程返回新的子进程的pid。父、子进程并不共享这些存储空间部分,通常父、子进程共享代码段;子进程继承父进程的以下属性:已打开的文件描述符 实际UID、GID,有效UID、GID 当前工作目录 根目录 文件创建UMASK 环境 资源限制
exec函数族
例6-3:用fork函数创建一个子进程,在子进程中。要求显示子进程号和父进程号,然后显示当前目录下的文件信息,在父进程中同样显示子进程号和父进程号;
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t result;
int newret;
result=fork();
if(result==-1)
{
printf("进程创建失败");
}else if(result==0)
{
printf("返回值是:%d,这是子进程,进程号是:%d\n",result,getpid());
printf("父进程号是:%d\n",getppid());
execl("/bin/ls","ls","-l",0);
}else
{
sleep(10);
printf("返回值是:%d,这是父进程,进程号是:%d\n",result,getpid());
printf("父进程号是:%d\n",getppid());
}
}
total后是ls列出的内容,
exec族的6个函数来建立子进程分别是execl、execv、execle、execve、execlp、execvp函数;其中l、e、v、p表示函数中的参数分别用列表传递方式、字符指针数组传递方式、可指定环境变量及路径自动搜索功能;
exec示例:
char *const ps_argv[]={"ps","-o","pid,ppid,pgrp,session,tpgid,comm",NULL};
char *const ps_envp[]={"PATH=/bin:/user/bin","TERM=console",NULL};
execl("/bin/ps","ps","-o","pid,ppid,pgrp,session,tpgid,comm",NULL);
execv("/bin/ps",ps_argv);
execle("/bin/ps","ps","-o","pid,ppid,pgrp,session,tpgid,comm",NULL,ps_envp);
execve("/bin/ps",ps_argv,ps_envp);
execlp("ps","ps","-o","pid,ppid,pgrp,session,tpgid,comm",NULL);
execvp("ps",ps_argv);
exec应用:
1.execv函数的使用,要在程序中执行命令:ps -ef,命令ps在’/bin‘目录下,在这一函数中,参数v表示参数传递(含命令)为构造指针数组方式;
char* arg[]={"ps","-ef",NULL};
函数的使用:
execv("/bin/ps",arg);
【ps命令参数】
-e 显示所有进程(等价于-A)
-f 全部列出,通常和其他选项联用】
//参考程序为:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
char *argv[]={"ps","-ef",NULL};
execv("/bin/ps",argv);
return 1;
}
2.execlp函数的使用,在程序中执行命令:ps -ef,命令ps在‘/bin’目录下,在这一函数中,参数l表示命令或参数逐个列举,参数p是文件查找方式(自动检索路径而不需要给出路径),函数调用形式:
execlp("ps","ps","-ef",NULL);
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
execlp("ps","ps","-ef",NULL);
return 1;
}
3.execl函数应用,在程序中执行命令:ps -ef,命令ps在‘/bin’目录下,在这一函数中,参数l表示命令或参数逐个列举,需要给定路径,函数调用形式如下:
execl("/bin/ps","ps","-ef",NULL);
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
execl("/bin/ps","ps","-ef",NULL);
return 1;
}
4.execle函数应用,execle("/bin/login","login","-p",username,NULL,envp);上述语句运行时,login提示用户输入密码,输入密码期间关闭终端的回显,然后验证密码的正确性,如果密码不正确,login终止,init会重新fork/exec一个getty进程。如果密码正确,login程序设置一些环境变量,并设置当前的工作目录位用户的主目录;
5.设计程序,在子进程中调用函数execl(''/bin/ps","ps","-ef",NULL),而在父进程中调用函数execle("/bin/env","env",NULL,envp),其中定义:char *envp={"PATH=/tmp","USER=liu",NULL};
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
char *envp[]={"PATH=/tmp","USER=liu",NULL};
pid_t result;
result=fork();
if(result==-1)
{
printf("创建进程失败!\n");
}else if(result==0)
{
printf("创建进程成功\n");
execl("/bin/ps","ps","-ef",NULL);
}else
{
sleep(5);
printf("父进程\n");
execle("/bin/env","env",NULL,envp);
}
return 1;
}
//测试程序,先输出“创建进程成功”,之后运行ps- ef命令,停顿5秒后,输出"父进程",显示env命令内容:
PATH=/tmp
USER=liu
进程终止的两种形式:
①正常终止:main返回、调用exit函数、调用_exit系统调用;
②异常终止:被信号终止、调用absort,使其产生SIGABRT信号;
exit函数、_exit系统调用区别:
设计程序使得子进程和父进程在显示输出一些文字后用_exit和exit函数终止进程;
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t result;
result=fork();
if(result==-1)
{
printf("创建进程失败!\n");
exit(0);
}else if(result==0)
{
printf("测试终止进程的_exit函数\n");
printf("目前为子进程,这一行我们用缓存!");
_exit(0);
}else
{
sleep(5);
printf("测试终止进程的exit函数\n");
printf("目前为父进程,这一行我们用缓存!");
exit(0);
}
return 1;
}
printf函数使用的是缓冲I/O方式,在遇到‘\n’换行符时,自动将缓冲区的记录读出;在调用exit函数时,会先调用退出处理函数,然后清理I/O缓冲区,最后执行exit系统调用,而_exit函数则是直接调用exit系统调用,因此无法正常输出缓冲区的内容;
如果在子进程中调用sleep(5),父进程不调用会出现:
XXX
僵尸进程
一个已经终止运行、但其父进程还没对其进行善后处理(获得终止子进程的有关信息、释放它所占有的资源)的进程;
使用fork创建的子进程,在父进程已经结束,但其还在继续运行,子进程进入无父进程的状态,称之为孤儿进程;孤儿进程会很快被init/systemd所收养;另外,为避免这种情况,可以通过在父进程中调用wait函数/waitpid函数,使得子进程比父进程早终止;
示例:僵尸进程的产生
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t result;
result=fork();
if(result==-1)
{
printf("创建进程失败!\n");
exit(0);
}else if(result==0)
{
printf("这是子进程,进程号是:%d\n",getpid());
}else
{
wait(NULL); //init系统消除僵尸进程;
sleep(20);
printf("这是父进程,进程号是:%d\n",getpid());
}
return 1;
}
一个程序退出时,它的子进程会被init进程继承,它是Linux启动后的第一个进程,init进程会自动清理所有它所继承的僵尸进程。
例:在父进程中调用wait函数等待子进程,父进程直到接收到子进程结束的信号后,父进程结束等待。设计一个程序,要求创建一个子进程,子进程显示自己的进程号pid后暂停一段时间,父进程等待子进程正常结束,打印显示等待的进程号pid和等待的进程退出状态。
//待定