Fork me on GitHub

Linux-进程控制

进程控制

进程创建
  • fork()

    1.以父进程为模板创建一个子进程,父子进程代码共享,数据独有(写时复制技术)
    2.fork的返回值:父进程返回子进程pid,子进程返回0
    3.fork之前父进程独立运行,fork之后父子两个执行流分别执行,运行先后取决于cpu调度
    

avater

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main()
{
pid_t pid = fork();
if(pid < 0){
printf("fork error!");
return -1;
}
else if(0 == pid){
printf("I am child: pid--%d\n , fork return--%d\n",getpid(),pid);
}
else{
printf("I am paretn! : pid--%d\n , fork return--%d\n",getpid(),pid);
}
sleep(1);
return 0;
}

avater

  • vfork():创建一个子进程

    1.子进程没有退出或者运行其他程序则父进程是阻塞的,也就意味着子进程是先运行的,
    子进程退出(不能再main函数中return)之后,父进程才能运行
    2.子进程先运行的原因:
    因为创建出子进程,大多时候是为了让它运行其他程序
    3.父进程阻塞的原因:
    vfork创建子进程后,父子进程是共用一块虚拟地址空间的
    

vfork设计出来的目的就是为了创建一个子进程,然后直接运行其他的程序,重新运行其他的程序就是重新给子进程开辟新的空间更新它自己的一份地址空间和页表

自从fork函数使用了写时拷贝技术之后,这个函数就淘汰了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
pid_t pid = vfork();

if(pid < 0){
perror("vfork error:");
return -1;
}
else if(0 == pid){
printf("this is child!\n");
}
else{
while(1){
printf("this is paretn!\n");
sleep(1);
}
}
return 0;
}

avater

进程终止
终止方式:
    1.main函数中return则是退出进程
    2.exit(int status) 库函数调用
        在程序任意位置调用都会使进程退出
        status是进程的退出状态
    3._exit(int status)系统调用接口
        在程序任意位置调用都会使进程退出
        exit最终调用的就是这个接口退出的
  • exit与_exit的区别:
  1. exit是温和性的退出,在退出前会温和的释放资源,刷新缓冲区
  2. _exit是暴力退出,直接释放资源退出,不会刷新缓冲区
  • 不管哪种主动退出方式,都会返回一个数字(通过echo $?命令查看进程退出码),这个数字是进程的退出状态,它表明了进程的退出原因
  1. 正常运行完毕,结果符合预期
  2. 正常运行完毕,结果不符合预期
  3. 异常退出,返回状态将不能作为评判标准
进程等待

一个进程退出之后因为要保存自己的退出原因,因此并不会释放所有的资源,它等着父进程查看它的退出原因,然后释放所有资源

假如父进程根本就没管,那么这个子进程就成了僵尸进程,危害就是资源泄露

因此为了防止出现僵尸进程,父进程就应该管一下子进程的退出

进程的等待就是等待子进程的状态改变,获取子进程的退出状态码

允许系统释放子进程的所有资源,这时候子进程的所有资源才会被释放掉

进程等待是避免产生僵尸进程的主要方式

  • 进程等待的方式:

    阻塞:一个函数为了完成功能,没有完成就不返回
    非阻塞:一个函数为了完成功能,但是没有完成也会立即返回
    1.pid_t wait(int *status)
        //status用于获取子进程退出状态码,如果不关心置NULL
        //返回值是返回退出的子进程pid
        wait函数目的就是为了等待任意一个子进程的退出,因为wait是一个阻塞型函数,因此如果没有子进程退出那么它就一直等待,不返回,直到有子进程退出
        如果没有子进程,则发生错误ECHILD
    2.pid_t waitpid(pid_t pid, int *status, int options);
        waitpid是个阻塞/非阻塞可选的函数
        //功能:默认等待子进程的退出
        //pid:    -1: 等待任意子进程  >0  等待指定的子进程
        //status:获取退出状态码
            WIFEXITED:判断通过wait获取的状态判断进程是否正常退出。
            WEXITSTATUS:正常退出则获取一下进程退出时返回的状态码
        //options:0是阻塞  WNOHANG:非阻塞,立即返回的,如果没有子进程退出则返回0,如果有则回收资源
        //返回值:-1:出错  ==0:没有子进程退出  >0:退出的子进程pid
    
  • wait获取的状态(status),使用了两个字节,这两个字接中:

    高8位存储进城推出时返回的状态码
    低7位存储的是引起进程异常退出的信号
    还有中间的一位是 core dump 标志
    

avater

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//这是一个阻塞的等待代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
pid_t pid = fork();
if(pid < 0){
perror("fork error");
exit(-1);
}
else if (0 == pid){
printf("this is child---%d\n",getpid());
sleep(5);
exit(99);
}
else{
printf("i am farther , wait....\n");
int status = 0;
pid_t id = -1;
if((id = wait(&status))>0){
printf("waiting success~~~\n");
sleep(1);
if(WIFEXITED(status)){
printf("child is exited: [%d]\n",WEXITSTATUS(status));
}
else{
printf("waitfalse");
return -1;
}
}
}
return 0;
}

avater

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//这是一个非阻塞等待,通过对waitpid第三个参数的设置来实现
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
pid_t pid = fork();
if(pid < 0){
perror("fork error:");
exit(-1);
}
else if (0 == pid){
printf("this is child---%d\n",getpid());
sleep(5);
exit(66);
}
else{
printf("father process\n");
pid_t id = -1;
int status = -1;
while((id = waitpid(pid,&status,WNOHANG))==0){
printf("fatherproc is running~~~\n ");
sleep(1);
}
if(WIFEXITED(status)){
printf("child is exited: [%d]\n",WEXITSTATUS(status));
}
else{
printf("return error\n");
return -1;
}
}
return 0;
}

avater

程序替换
概念:相当于让虚拟地址空间中的代码段地址指向了物理内存的另一段代码位置
这样的话虚拟地址空间中原先的数据区域以及堆栈都会重新初始化,因为现在的代码运行的根本不是复制的那些数据
但是这个进程的pcb还是原来的pcb

avater

  • exec函数族:

    execl    exexlp    execle
    execv    execvp    execve
    
    l和v的区别:l是参数平铺一个一个通过exec函数参数赋予,v参数直接使用字符串指针数组
    execl/execv  需要我们给出要替换的程序的全路径名
    execlp/execvp:只需要给出要替换的程序的名称就行
    execle/execve:重新自己组织换将变量,不使用现有的
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//exec函数的演示,这里只对几个函数进行演示
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
pid_t pid = fork();
if(pid < 0){
perror("fork error:");
return -1;
}
else if(0 == pid){
printf("\n-------------------------\n");
//最后一个参数必须使用 NULL 来进行结尾
//execl("/bin/ls","ls","-l","-a",NULL);

//execlp 会从 PATH 环境变量所指的目录中查找符合参数 file 的文件名,找到然后执行该文件的 argv[0],argv[1]….
//execlp("ps","ps","-ef",NULL);

//这份我们自己维护的环境变量是用来传递给替换后的新程序的,所以本程序依然需要写上绝对路径
char *ptr = "PATH=i-am-a-student";
char *env[3] = {NULL};
env[0] = ptr;
execle("/home/lyz/WorkSpace/pctrl/test_10_17/pctrl/test","test",NULL,env);
//替换之后,下面的语句不会打印
printf("hello world!\n");
}
return 0;
}
1
2
3
4
5
6
7
8
9
//test.c
#include<stdio.h>
#include<unistd.h>

int main()
{
printf("%s\n",getenv("PATH"));
return 0;
}

avater

avater

avater

-------------本文结束感谢您的阅读-------------

本文标题:Linux-进程控制

文章作者:李煜哲

发布时间:2018年12月13日 - 20:12

最后更新:2018年12月13日 - 20:12

原始链接:http://yoursite.com/2018/12/13/Linux-进程控制/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者

觉得好的话就打赏一下吧