Linux进程控制(一)

Linux进程控制

  • 1.fork函数初识
  • 2.写时拷贝
  • 3.fork常规用法
  • 4.fork调用失败的原因
  • 5.进程终止
  • 6.进程常见退出方法
    • 6.1 _exit
    • 6.2 exit
    • 6.3 return退出
  • 7.进程等待
    • 7.1进程等待的必要性
    • 7.2进程等待的方法

hello,各位读者大大们你们好呀
系列专栏:【Linux的学习】
本篇内容:fork函数初识;写时拷贝;fork常规用法;fork调用失败原因;进程终止;进程常见退出方法;进程等待
⬆⬆⬆⬆上一篇:程序地址空间
作者简介:轩情吖,请多多指教(> •̀֊•́ ) ̖́-

1.fork函数初识

#include
pid_t fork(void);

fork从已存在进程中创建一个新的进程。新进程为子进程,而原进程为父进程
返回值:子进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程
添加子进程到系统进程列表中
fork返回,开始调度器调用
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定
Linux进程控制(一)_第1张图片

#include 
  2 #include <unistd.h>
  3 int main()
  4 {
  5 int ret=fork();
  6 if(ret>0)
  7 {
  8   //父进程
  9   while(1)
 10   {
 11   printf("I am parent task, my pid is %d,my ppid is %d\n",getpid(),getppid());
 12   sleep(1);
 13   }
 14 }
 15 
 16 if(ret==0)
 17 {
 18 //子进程
 19   while(1)
 20   {
 21 printf("I am child task,my pid is %d,my ppid is %d\n",getpid(),getppid());
 22   sleep(1);
 23   }                                                                                                                                                                 
 24 }
 25   return 0;
 26 }


Linux进程控制(一)_第2张图片

2.写时拷贝

父子代码共享,父子在不写入时,数据也是共享的,当任何一方试图写入,便以写时拷贝的方式各自一份副本
Linux进程控制(一)_第3张图片

#include 
  2 #include <unistd.h>
  3 int main()
  4 {
  5 int ret=fork();
  6 int num=100;
  7 if(ret>0)
  8 {
  9   //父进程
 10   while(1)
 11   {
 12   printf("I am parent task, my pid is %d,my ppid is %d,num=%d,&num=%p\n",getpid(),getppid(),num,&num);
 13   sleep(1);
 14   }
 15 }
 16 
 17 if(ret==0)
 18 {
 19 //子进程
 20 num=50;//在执行子进程时修改num的值                                                                                                                                  
 21   while(1)
 22   {
 23 printf("I am child task,my pid is %d,my ppid is %d,num=%d,&num=%p\n",getpid(),getppid(),num,&num);
 24   sleep(1);
 25   }
 26 }
 27   return 0;
 28 }

Linux进程控制(一)_第4张图片
可以发现在子进程中修改了num的值,但是父进程却没有变化,并且他们的地址还是相同的,因此这里的地址不是真实的物理地址,而是虚拟地址,这一个我在另一个博客中提到过,不理解的可以去看一下,其实本质上就是发生了写时拷贝,当还没有修改num的值时,父进程和子进程共享同一份代码和数据,但是当子进程修改num的值时,对于子进程而言,页表所指向的物理地址发生了改变,在另一个地方修改了num的值。

3.fork常规用法

①一个父进程希望复制自己,使父子进程同时执行不同的代码段
②一个进程要执行一个不同的程序

4.fork调用失败的原因

①系统中有太多进程
②实际用户的进程超过了限制

5.进程终止

在进程退出时,会有三种情况:代码运行完毕,结果正确;代码运行完毕,结果不正确;代码异常终止
代码异常终止本质上就是因为某些原因,导致进程收到了来自操作系统的信号

6.进程常见退出方法

首先讲一下如果查看进程退出码

使用命令echo $?

Linux进程控制(一)_第5张图片

Linux进程控制(一)_第6张图片
上图用了最简单的一个代码展示了如果查看程序退出码,return返回的就是程序的退出码,程序的退出码是为了供用户进行程序退出的健康状态的判断,也就是查看代码运行完毕,结果是否正确

6.1 _exit

void _exit(int status);
头文件:#include
参数:status定义了进程的终止状态,父进程通过wait来获取该值(这一个我们后面再讲)
Linux进程控制(一)_第7张图片

#include 
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <stdlib.h>
  5 void Function()
  6 {
  7                                                                                                                                                                     
  8 printf("I am a function\n");
  9 _exit(10);
 10 }
 11 int main()
 12 {
 13   Function();
 14 printf("I am a task\n");
 15   exit(-1);
 16 }

Linux进程控制(一)_第8张图片
可以看见我们的进程直接结束了,并且通过exit,我们的退出码为10
Linux进程控制(一)_第9张图片
再观察这个代码,去掉了换行,来看一下他的结果
在这里插入图片描述
可以发现它并不会打印出我们想要的内容,但是exit并不会这样,exit会在此前先刷新缓冲区,具体往后看

6.2 exit

exit(int code):code代表就是进程的退出码
需要使用头文件#include
exit最后也会调用_exit,但是在调用exit之前,还做了其他工作:
①执行用户通过atexit或on_exit定义的清理函数
②关闭所有打开的流,所有的缓存数据均被写入
③调用_exit
Linux进程控制(一)_第10张图片
Linux进程控制(一)_第11张图片
可以发现,进程直接在exit(0)处结束了,并且他的退出码是0
Linux进程控制(一)_第12张图片

在这里插入图片描述
注意观察这个代码,与之前相比,少了换行,但是最后还是打印出了内容,这也就印证了上面的结论

6.3 return退出

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时,函数会将main的返回值当做exit的参数,这个就不多讲了

7.进程等待

7.1进程等待的必要性

①子进程退出,父进程不管不顾,就可能造成“僵尸进程”的问题,进而造成内存泄露,进程一旦变成“僵尸进程”,kill也无能为力
②父进程派给子进程的任务完成的如何,我们需要知道
③父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

7.2进程等待的方法

wait方法
pid_t wait(int* status);
返回值:成功返回被等待进程的pid,失败返回-1
参数:输出型参数,获取子进程退出状态,不关心则可以设置NULL
Linux进程控制(一)_第13张图片
Linux进程控制(一)_第14张图片
可以发现,父进程一直等到子进程被杀掉才继续往后执行,说明wait一直在等待子进程结束,通过查看进程的信息,可以发现没有僵尸进程的存在,这是wait起了作用

waitpid方法
pid_t waitpid(pid_t pid,int* status,int options);
返回值:
①当正常返回时,waitpid返回收集到的子进程的进程pid
②如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
③如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在
参数:
pid:
pid=-1:等待任意一个子进程。与wait等效
pid>0:等待其进程pid与pid相等的子进程
status:
不要当做一个完整的整数,而应该看做位图
wait和waitpid都有一个status参数,该参数是一个输出型参数,由操作系统填充,如果传递NULL,表示不关心子进程的退出状态信息
Linux进程控制(一)_第15张图片
我们暂时先把waitpid的第三个参数写成0,这里主要研究的是status,前面也说了,他是个输出型参数,我们只研究status低16位比特位
Linux进程控制(一)_第16张图片
当信号为0时表示没有收到信号,正常退出
Linux进程控制(一)_第17张图片
Linux进程控制(一)_第18张图片
Linux进程控制(一)_第19张图片
观察上面的代码和现象,(status>>8)&0xFF表示的是右移8位,然后再按位与,正好得出的结果是status低16位的前8位,得到了退出码,而status&0x7F也是如此,得出了所接收到的信号。在上面的代码中,使用kill -9终止了子进程的运行,所以说子进程收到了来自操作系统的信号,因此打印出的终止信号为9。同时要注意一个点,当终止信号不为0的情况下,无论它的退出码是多少,都是没有意义的。

接下来讲一下waitpid的第三个参数,当他为WNOHANG时,若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回子进程的id,也就是说父进程在没有接收到子进程的退出信息时,也可以先做别的事情,等到子进程结束了,再接收其退出信息。
Linux进程控制(一)_第20张图片

只要当waitpid的返回值为0时,他就会执行所对应的代码

这里再介绍一种获取程序退出码和终止信号的方法
WIFEXITEND(status):若为正常终止子进程返回的状态则为真。(查看进程是否正常退出)
WEXITSTATUS(status):若WIFEXITED非0,提取子进程的退出码
Linux进程控制(一)_第21张图片
在之前的代码上稍作了修改
在这里插入图片描述

Linux进程控制(一)_第22张图片

Linux进程控制(一)的知识大概就讲到这里啦,博主后续会继续更新更多Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!

你可能感兴趣的:(Linux的学习,linux,运维,服务器,fork,进程)