linux系统的进程控制相关
1.fork()函数
函数原型 :pid_t fork(void);
函数作用 :用于创建一个进程,所创建的子进程复制父进程的代码段/数据段/BSS段/堆/栈等所有用户空间信息;在内核中操作系统重新为其申请了一个PCB,并使用父进程的PCB进行初始化;
如图:
注意:当执行/a.out中有fork子进程时,
shell进程将不知道a.out创建了子进程,因此在shell检测到父进程执行完毕后,shell会直接切换到前台。因此当父进程比子进程先抢到cpu进行处理时,shell将不会管子进程而直接返回到前台。
避免这种情况的发生,可以让父进程sleep一会即可。
函数返回值: >0,为父进程返回值。=0,子进程返回值。<0,fork失败。显然我们可以根据fork函数的返回值来判断当前进程是父进程还是子进程。
子进程创建成功后,代码的执行位置 :父进程执行到哪里,子进程就从哪里开始执行(fork以后)
如图:
父子进程的执行顺序 :父子进程的执行顺序是什么呢?答案是不确定的。因为进程之间是相互竞争去抢CPU的。谁抢到是不一定的。
怎么得到进程的ID:使用getpid()得到当前进程的id,使用getppid得到当前进程的父进程的 id
fork函数的使用1—创建一个子进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
int n;
pid = fork();
if(pid < 0)
{
perror("fork");
exit(1);
}
if(pid == 0)
{
printf("this is child\n");
}
if(pid > 0)
{
printf("this is parent\n");
sleep(1);
}
printf("=======1======\n");
printf("=======2======\n");
printf("=======3======\n");
printf("=======4======\n");
return 0;
}
fork函数使用–循环创建多个子进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int i = 0;
int count = 200;//进行全局变量是否能够在父子进程之间进行传递的测试
int number = 3;//生成的子进程的数量
pid_t pid;//fork返回值的定义
for ( ; i<number; i++)
{
pid = fork();
if(pid == 0)break;
}
if(i == 0)
{
printf("this is first child id=%d pid=%d",getpid(),getppid());
count+=200;
printf("count = %d\n",count);
}
if(i == 1)
{
printf("this is second child id=%d pid=%d",getpid(),getppid());
count+=200;
printf("count = %d\n",count);
}
if(i == 2)
{
printf("this is third child id=%d pid=%d",getpid(),getppid());
count+=200;
printf("count = %d\n",count);
}
if(i == 3)
{
printf("this is parent id=%d",getpid());
count+=400;
printf("count = %d\n",count);
sleep(1);
}
return 0;
}
上面的代码所战士的结果为:
显然,这验证了全局变量不能在父子进程之间进行传递。即进程之间不共享全局变量。Why???
如图:
如图,全局变量num=100,进程之间对全局变量是读时共享,写时复制的原则。在读num时,都是读取到num=100,但是在对内存中的num进行写时,会将num=100复制一份,然后分别对num进行对应进程的操作后,分别写到对应的num中。在父进程num=99写到父进程num中,子进程num=101写到子进程num中。
2.exec函数族
exec函数族不是一个函数。在linux系统中,exec函数族包括:execl,execv,execle,execve,execlp,execvp函数。针对这几个函数的用法及介绍将在下面介绍。
exec函数族目的是为了在fork()后,让进程执行另外一些我们想要执行的程序。exec函数族能够根据指定的文件名或目录名找到可执行文件,并替换进程地址空间中的数据段、代码段、堆栈段。
如下图:
使用exec函数族的场景:
1.当一个进程想要执行另一个进程时,此时利用fork建立子进程,再在子进程中调用exec函数族中的函数来使子进程执行我们想要执行的进程。
2.当进程认为自己不能为系统和用户做出贡献时,就可以调用exec函数族中的函数,让自己执行另一个想要执行的进程。
使用示例
这里先简单介绍一下execl函数,其他函数将在下面详述:
int execl(const char path, const chararg, …);一般用来执行自定义的应用程序
- path: 要执行的程序的绝对路径
- 变参arg: 要执行的程序的需要的参数
- 第一arg:占位(不能空,随便写点,一般系写命令的名字)
- 后边的arg: 命令的参数
- 参数写完之后: NULL
- 一般执行自己写的程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
pid_t pid = fork();
if(pid == 0)
{
execl("hellotest","hellotest",NULL);
perror("execl");
exit(1);
}
if(pid > 0)
{
printf("parent pid=%d\n ",getpid());
}
}
以上代码是为了让子进程执行当前文件夹下的hellotest程序。结果为:
exec函数族—6个exec函数
<1>执行指定目录下的程序:execl,execv函数:
- int execl(const char *path, const char *arg,…);
path: 要执行的程序的绝对路径
变参arg: 要执行的程序的需要的参数
第一arg:占位(不能空,随便写点,一般系写命令的名字)
后边的arg: 命令的参数
参数写完之后: NULL
一般执行自己写的程序 - int execv(const char *path, char const argv[]);
参数:
path:指定程序的路径,argv:用户传递的参数
示例:
path = /bin/ps
char args[] = {“ps”,“aux”, NULL};
execv("/bin/ps", args);
<2> 执行PATH环境变量能够搜索到的程序
- int execlp(const char file, const chararg, …);
参数:
file: 执行的命令的名字(直接写命令即可)
第一arg:占位
后边的arg: 命令的参数
参数写完之后: NULL
该函数是执行系统自带的程序:
1.在/bin
2. execlp执行自定义的程序: file参数为绝对路径 - int execvp(const char *file, char *const argv[]);
类似于execv与execl的关系
<3> 执行指定路径, 指定环境变量下的程序
- int execle(const char *path, const char *arg, …, char const envp[]);
path: 执行的程序的绝对路径
如:/home/itcast/a.out
arg: 执行的的程序的参数
envp: 用户自己指定的搜索目录, 替代PATH
如:char envp[] ={"/home/itcast", “/bin”, NULL}; - int execve(const char *path, char *const argv[], char *const envp[]);
补充:对于PATH环境变量的补充:
原文出处:http://c.biancheng.net/view/5970.html
3.进程回收
由于代码编写的不完善,随着时间的推移,应用程序的性能会越来越低,有时会陷于某一循环中,导致不必要的 CPU 负载。这些应用程序还可能导致内存泄漏,这时应用程序不再将不需要的内存释放回操作系统。这些应用程序可能会导致服务器停止运行,因此需要重新启动服务器。进程回收就是为解决这些问题而创建的。
导致这些问题出现的一些问题进程:
孤儿进程
- 父生子
- 父先死,子还活着,则子进程称为孤儿进程
- 孤儿进程被系统的init进程(Ubuntu16 以后就变成了UI进程)领养,则init进程就变成了孤儿进程的父进程,这样的目的是为了释放子进程占用的系统资源。(进程结束之后,能够释放用户区空间,但是释放不了PCB,必须由父进程释放)
- 示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
pid_t pid = fork();
if(pid == 0)
{
printf("child pid=%d ppid=%d \n",getpid(),getppid());
//因为父进程死亡,则getppid得到的为UI进程的pid
sleep(1);//给子进程sleep,不给父进程sleep那么就可以实现父进程死了,但子进程还存在的情况。
}
else
printf("parent pid=%d\n",getpid());
return 0;
}
僵尸进程
- 孩子死了,父亲还活着,但是父进程不去释放子进程的PCB,这样的子进程称为僵尸进程。
- 僵尸进程是一个死进程,不是一个正常活着的进程。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
pid_t pid = fork();
if(pid == 0)
{
printf("child pid=%d ppid=%d \n",getpid(),getppid());
}
else
{
while(1)
{
printf("parent pid=%d\n",getpid());
sleep(1);
}//让父亲一直不死,释放不了孩子的空间
}
return 0;
}
在ps anux打印出的信息中就有一个Z+即僵尸进程的首字母,如图:
怎么结束僵尸进程?
杀死父进程-------那么僵尸进程就变为孤儿进程,就可以被领养,接着释放。
kill -9 4898
如何进行进程回收工作
linux提供了两个函数来实现进程回收工作:pid_t wait(int *status);pid_t waitpid(pid_t pid,int *statuc,int options);
wait函数
- wait函数是一个阻塞函数,阻塞条件为:子进程是否死亡
- 调用一次回收一个子进程资源。
- 返回值:
负1:回收失败,则说明已经没有子进程了
大于0:回收子进程对应的pid - 参数:status–传出参数
判断子进程是如何死的?
(1)获取退出时候的返回值(正常退出)
如:return 0;exit(0);
//若正常退出,则WIFEXITED(status) > 0
//那么可以使用WEXITSTATUS(status)来获得结束状态
(2)被哪个信号杀死,如:kill中的信号,9为杀死进程的信号
//若被某个信号杀死则WIFSIGNALED(status) > 0
//使用 WTERMSING(status)来得到是什么信号杀死了进程
示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
exit(1);
}
if(pid > 0)
{
printf("this is parent\n");
int status;
wait(&status);
if(WIFEXITED(status))
{
printf("exit status is %d\n",WEXITSTATUS(status));
}//正常杀死
if(WIFSIGNALED(status))
{
printf("exit single id %d\n",WTERMSIG(status));
}//通过信号杀死
}
if(pid == 0)
{printf("this is child\n");sleep(200);}//为了测试被信号杀死,因此让子进程先睡200秒,然后用kill -9杀死子进程
return 2;
}
waitpid函数
id_t waitpid(pid_t pid, int *status, int options);使用该函数不仅仅只回收一个子进程
- 返回值:
负1:回收失败,没有子进程
大于0:被回收子进程的pid
如果为非阻塞:=0:子进程正处于运行态。 - 参数
(1)pid:
大于0:某个子进程的pid
等于-1:回收所有子进程,如,可以用来循环回收所有子进程:
while( (wpid =waitpid(-1, &status, WNOHANG) != -1))
等于0:回收当前进程组的所有子进程,意思就是当父进程的子进程送给别的父进程,此时回收的话只回收当前父进程组内的所有子进程,但不包括送出去的子进程。
小于0:该参数为子进程的pid取反(加减号),也就是当父进程想要回收送出去的子进程时,只需要在送出去的子进程pid前加减号即可。
(2)options:
等于0:waitpid为阻塞状态
WNOHANG:waitpid为非阻塞状态
示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
int i = 0;
pid_t pid;
for(;i < 3;++i)
{
pid = fork();
if(pid == 0)break;
}
if(i == 0)
{
execl("/home/sun123/sunjiasen/day5/error","error",NULL);
perror("execl1");
exit(1);
}
else if(i == 1)
{
execlp("ls","ls","-l",NULL);
perror("execlp");
exit(1);
}
else if(i == 2)
{
execl("/home/sun123/sunjiasen/day5/test","test",NULL);
perror("execl2");
exit(1);
}
else if(i == 3)
{
printf("tnis is parent pid=%d\n",getpid());
int status;
pid_t wpid;
while((wpid =waitpid(-1, &status, WNOHANG) != -1))
{
printf("%dth child reversed",wpid);
if(WIFEXITED(status))
{
printf("return value is %d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status))
{
printf("killed by%d single\n",WTERMSIG(status));
}
}
}
return 0;
}