Linux-进程创建,进程终止,进程等待

文章目录:

  • 1.进程创建
    • 1.1 fork()函数
    • 1.2 vfork()函数
  • 2.进程终止
    • 2.1 进程终止的场景
    • 2.2 进程常见的终止方法
      • 正常终止(可以通过 echo $? 查看进程退出码)
      • 异常退出:Ctrl + c,信号终止
      • 扩展-->[刷新缓冲区的方式]
      • 扩展-->[库函数在哪一步冲刷缓冲区,关闭流]
  • 3. 进程等待
    • 3.1 进程等待的作用
    • 3.2 wait()函数
      • wait函数的使用
      • 问题一:如下代码我们如何证明父进程没有先退出,而是一直在等待子进程先退出
      • 问题二:僵尸进程,子进程在退出的时候会告知父进程,是怎么实现的?
    • 3.3 waitpid()函数

1.进程创建

此处主要讲 fork()函数 和 vfork()函数

1.1 fork()函数

fork()函数的一些基本概念我们在前面fork()函数中讲过,如不了解可点击查看,此次我们主要对上篇没有说到的fork()函数的内容进行补充说明

fork()函数

 
• 子进程是拷贝父进程的PCB的,子进程的大部分数据是来源于父进程的。   eg:内存指针(数据段,代码段),页表
 
• 父进程创建子进程成功之后,父子进程是独立的两个进程(进程的独立性),父子进程的调度取决于操作系统内核
 
• 进程是抢占式执行的,父子进程谁先运行是不确定的
 
• 写时拷贝
Linux-进程创建,进程终止,进程等待_第1张图片

fork失败的原因:

系统中有太多的进程
 
使用top命令可查看系统中的进程数
Linux-进程创建,进程终止,进程等待_第2张图片
 
实际用户的进程数超过了限制
 
使用 ulimit -a 命令可查看最大进程限制为7261
Linux-进程创建,进程终止,进程等待_第3张图片

1.2 vfork()函数

父进程指向一个进程虚拟地址空间,父进程调用vfork()函数创建一个子进程也指向同一个进程虚拟地址空间,此时会出现压栈问题
 
• vfork()函数存在调用栈混乱的问题
 
• 解决方法:子进程先调用,子进程调用完毕之后,父进程再调用
Linux-进程创建,进程终止,进程等待_第4张图片

2.进程终止

2.1 进程终止的场景

•  从main函数的return返回
 
   情况1:代码执行完毕,结果正确
 
   情况2:代码执行完毕,结果不正确
 
• 程序崩溃

2.2 进程常见的终止方法

正常终止(可以通过 echo $? 查看进程退出码)

从main函数的return返回
 
测试如下:
Linux-进程创建,进程终止,进程等待_第5张图片
在这里插入图片描述

调用exit函数----库函数
 
void exit(int status); //status退出码
 
测试如下:
 
创建一个main函数写入exit函数,会发现函数运行到exit就结束,不会运行exit后面的代码Linux-进程创建,进程终止,进程等待_第6张图片
在这里插入图片描述
如果将代码改成如下,去掉hello后面的\n再次运行,会发现hello依旧可以运行出来
Linux-进程创建,进程终止,进程等待_第7张图片
Linux-进程创建,进程终止,进程等待_第8张图片
因为exit函数可以刷新缓冲区所以,有没有\n都如上代码的输出结果并没有影响

调用_exit函数----系统调用函数
 
void _exit(int status); //status退出码
 
测试如下:
 
创建一个main函数写入_exit函数,会发现函数运行到_exit就结束,不会运行_exit后面的代码

Linux-进程创建,进程终止,进程等待_第9张图片
在这里插入图片描述
如果将代码改成如下,去掉hello后面的\n再次运行,会发现并不会有结果输出
Linux-进程创建,进程终止,进程等待_第10张图片
在这里插入图片描述
那么为什么会出现如上的情况呢?
 
因为\n可以刷新缓冲区,没有\n,_exit函数并不能刷新缓冲区,所以没有结果输出

对比上面exit函数和_exit函数的运行结果会发现:

    •   exit函数会刷新缓冲区
    •   _exit函数不会刷新缓冲区
Linux-进程创建,进程终止,进程等待_第11张图片
执行用户定义的清理函数: atexit函数
 
`int atexit(void (*function)(void));
 

• 参数是一个函数指针,接收一个函数地址
 
测试入下:
 
程序会按如下箭头进行运行,,所以运行结果应先打印“hello”,再打印出“我是func”Linux-进程创建,进程终止,进程等待_第12张图片
Linux-进程创建,进程终止,进程等待_第13张图片
若在exit函数上面加一个死循环,则程序将不会运行到exit函数,我们猜想程序会按如下图所示箭头指向运行,所以运行结果应该只是打印出“hello”然后进行死循环
Linux-进程创建,进程终止,进程等待_第14张图片
Linux-进程创建,进程终止,进程等待_第15张图片
通过以上两种测试结果可以证明传入atexit函数func函数是一个回调函数
       回调函数的执行相当于定闹铃,定闹铃的时候闹铃不会响,只有到定的时间闹铃才会响;atexit注册传入func这个函数的时候,并不会调用这个函数,只有当程序终止的时候才会调用这个函数

异常退出:Ctrl + c,信号终止

扩展–>[刷新缓冲区的方式]

•    “\n”
•    fflush函数
•     exit()函数
•     main()函数的return

扩展–>[库函数在哪一步冲刷缓冲区,关闭流]

Linux-进程创建,进程终止,进程等待_第16张图片

3. 进程等待

3.1 进程等待的作用

父进程进行等待,子进程先于父进程退出,由于父进程在等待子进程,所以父进程会回收子进程的退出资源,从而防止进程产生僵尸进程

3.2 wait()函数

pid_t wait(int *status);

返回值:

        •    成功:返回子进程的pid号
 
        •   失败:返回 -1
 

参数:整形指针;保存的status占4个字节

Linux-进程创建,进程终止,进程等待_第17张图片
coredump标志位:
 
    •    如果取值为1,则表示当中的进程是由coredump(核心转储文件)产生
 
    •    如果取值为0,则表示当前进程没有coredump产生
 
终止信号:当前的程序是由什么信号导致的终止
 
正常情况:看进程退出码
Linux-进程创建,进程终止,进程等待_第18张图片
异常情况:看coredump标志位和终止信号
Linux-进程创建,进程终止,进程等待_第19张图片

拓展:出参

参数int* status 是应该出参
      调用者准备应该int类型的变量,将地址传递给wait函数,wait函数在自己实现的内部,进行赋值,当wait函数返回之后,调用者就可以通过int变量获取进程退出的信息

wait函数的使用

首先我们创建一个僵尸进程,不使用wait函数
Linux-进程创建,进程终止,进程等待_第20张图片
Linux-进程创建,进程终止,进程等待_第21张图片
在这里插入图片描述
如上测试是一个僵尸进程,当我们加上wait函数后测试结果如下:
Linux-进程创建,进程终止,进程等待_第22张图片
Linux-进程创建,进程终止,进程等待_第23张图片
在这里插入图片描述
我们可以看到僵尸进程没有了

问题一:如下代码我们如何证明父进程没有先退出,而是一直在等待子进程先退出

Linux-进程创建,进程终止,进程等待_第24张图片
我们可以在子进程中加一个死循环,让子进程不退出,看父进程会不会退出,我们的猜想加上wait函数后,子进程不退出,父进程会一直等待
Linux-进程创建,进程终止,进程等待_第25张图片
我们可以通过=》查看进程调用堆栈命令:pstack
 
    •    pstack [pid]
 
来查看父进程现在在做什么,如果查看到父进程在进行wait则符合我
们之前的猜想
在这里插入图片描述
如上父进程的状态称为阻塞状态
 
阻塞状态:当前执行流调用某一个函数,该函数一直不返回,称这个现象为阻塞

问题二:僵尸进程,子进程在退出的时候会告知父进程,是怎么实现的?

子进程在退出的时候,会给父进程发一个信号,这个信号是SIGCHLD,而wait的实现内部就是判断是否收到了SIGCHLD信号,如果收到了SIGCHLD信号就把子进程的资源回收掉
Linux-进程创建,进程终止,进程等待_第26张图片

1.如何获取进程退出码:(status >> 8) & 0xff
2.如何获取终止信号:status & 0x7f
3.如何获取coredump标志位:(status >> 7) & 0x1

3.3 waitpid()函数

pid_t waitpid(pid_t pid, int *status, int options);
 
pid:
 
    •    -1:等待任一子进程,一旦等待到了,就返回
 
    •    0:等待特定子进程,>0的值,就是子进程的pid号
 
status:出参,同wait函数
 
options:
 
    •    WNOHANG:非阻塞等待方式(需要搭配循环去使用,直到完成函数功能),目的没完成
 
    •    0:阻塞等待方式
 
waitpid返回值
 

  • 当没有等待到相应的子进程,waitpid返回值为0
     
  • 等待到相应的子进程,waitpid的返回值为等待到的进程pid号(及大于0的)

测试如下:

    #include
  2 #include<sys/wait.h>
  3 #include<unistd.h>
  4 #include<stdlib.h>
       //1.创建子进程, 模拟让子进程先于父进程退出
       //2.父进程的逻辑当中执行进程等待
       //非阻塞模式的等待方式
  5 int main()
  6 {
  7     pid_t pid = fork();
  8     if(pid < 0)                                                                                                                                     
  9     {
 10         perror("fork");
 11         return 0;
 12     }      
 13     else if(pid == 0)                                                                
 14     {                                                    
 15         //child                       
 16         printf("i am child,pid is %d,ppid is %d\n",getpid(),getppid());
 17         sleep(5);                                 
 18         exit(1);                    
 19     }                     
 20     else                                
 21     {                  
 22         //father
            //waitpid的返回值:
            //当没有等待到相应的子进程, waitpid的返回值为0
            //等待到相应的子进程, waitpid的返回值为等待到的进程PID号(大于0)
 23         pid_t ret = 0;                                             
 24         do                                                       
 25         {                                                 
 26             ret = waitpid(-1,NULL,WNOHANG);
 27             if(ret == 0)                                 
 28             {                                 
 29                 printf("sub process is running...\n");                                                               
 30             }         
 31             sleep(1);
            }while(ret == 0);
 33         printf("i am father,wait sub process id is %d\n",ret);
 34         sleep(10);
 35         printf("i am father,pid is %d,exit...",getpid());
 36     }
 37     return 0;
 38 }

Linux-进程创建,进程终止,进程等待_第27张图片
在这里插入图片描述

你可能感兴趣的:(linux—系统,linux,进程创建,进程终止,进程等待,wait)