Linux进程控制之进程创建、终止、等待

(。・∀・)ノ゙嗨!你好这里是ky233的主页ky233_-CSDN博客Linux初识进程,C++类和对象中,C++类和对象上篇https://blog.csdn.net/ky233?type=blog

点个关注不迷路⌯'▾'⌯

目录

一、进程创建

1、fork函数的认识

1.请你描述一下,fork创建子进程,操作系统都做了什么?

2.fork之后,父子进程的代码是共享的,那么是只有之后共享的还是全部都是共享的呢? 

2.写时拷贝

二、进程终止

1.进程终止时,操作系统做了什么?

2.进程终止的常见方式

1.那么main函数为什么最后都要return 0呢?

2.第三种情况

3.如何用代码终止一个进程

1.什么是一个正确的终止

三、进程等待

1.为什么要有进程等待

2.wait和waitpid

1.wait

2.waitpid

3.父进程是怎么拿到这子进程的退出结果的呢?


一、进程创建

1、fork函数的认识

由于之前讲过一部分的fork,这里就不再过多的介绍,只是单独说一下面试题

1.请你描述一下,fork创建子进程,操作系统都做了什么?

创建子进程,给子进程分配相应的内核数据结构,必须让子进程自己独立,因为进程是具有独立性的,所以理论上子进程要有自己的代码和数据,可是我们没有加载的过程,那么子进程借用使用父进程的代码,而数据是可以被修改的所以必须要父子进程分离

2.fork之后,父子进程的代码是共享的,那么是只有之后共享的还是全部都是共享的呢? 

 这里要注意,在使用fork的时候,并不是fork之后的代码才被共享,而是所有的代码都会被共享

这是因进程随时都有可能被中断,下次回来必须要从之前的位置继续运行,所以CPU内有对应的寄存器数据,用来记录当前进程执行的位置,又因为,寄存器内的数据是可以有多份的,所以创建的时候,虽然父子进程各自调度,但是因为fork寄存器中的父进程在寄存器中保存的上下文也一同被子进程共享了,所以fork之后子进程就运行的是fork之后的代码了。(也就是说,创建的时候连父进程执行的位置也一起复制了,所以就接着执行了,而不代表着子进程不能访问之前的数据)

2.写时拷贝

不知道大家还记得不记得,fork函数的父子进程的同一个数据,在同一地址,同一时间访问,有两个不一样的值,这个就叫做发生了写时拷贝。

那么为什么要有写实拷贝呢?

是因为创建子进程的时候,不需要将不会访问的或者只读取的数据拷贝,但是OS也不知道什么样的数据会被写入,即便是知道,那么拷贝了就会提前写入吗?也是不一定的,所以操作系统采用了写时拷贝来将父子进程的数据进行分离!这是一种高效的使用内存的方法!!

Linux进程控制之进程创建、终止、等待_第1张图片

这张图说的很清楚,在子进程刚创建的时候,我们的父子进程是完全一样的,当子进程要去修改物理内存的内容的时候,会拷贝一份所共享的数据,然后修改页表的指向。

所以为什么要有写实拷贝总结:

  • 因为有写时拷贝的存在,所以父子进程的以彻底分离!完成了进程独立性的技术保证。
  • 写时拷贝是一种延迟申请技术,可以提高整体机能

二、进程终止

1.进程终止时,操作系统做了什么?

这个话题很简单,当然是释放进程申请的相关内核数据结构和对应的数据代码,本质来说就是一种资源的释放。

2.进程终止的常见方式

  1. 代码跑完,结果正确。
  2. 代码跑完,结果不正确。
  3. 代码没跑完,结果不正确。

前两个问题可以一起讨论,

1.那么main函数为什么最后都要return 0呢?

  1 #include 
  2 int main()
  3 {
  4     printf("hello Linux\n");
  5 
  6     return 10;
  7 }  

为什么代码最后总是0呢?

但其实main函数的返回值并不总是0,这个是进程的退出码。其中0表示结果正确,非0表示结果不正确。

我们可以通过下面这个命令来获取最近一个进程的退出码:echo $?

Linux进程控制之进程创建、终止、等待_第2张图片

这是返回给上一进程用来评判该进程执行结果用的。我们可以对返回值做判断来判别出你写的代码是否正确,如返回值正确,是自己想要的结果就是0,如不是则if直接返回一个非0的数字,非0值有无数个,以此来表示不同错误的原因,从而方便定位错误的原因。

我们可以用strerror来打印常见的错误码

  4 int main()
  5 {
  6     int i = 0;
  7     for(i=0;i<150;i++)
  8     {
  9        printf("%d:%s\n",i,strerror(i));                                                                             
 10 
 11     }
 12    // printf("hello Linux\n");
 13 
 14     return 0;
 15 }

Linux进程控制之进程创建、终止、等待_第3张图片 我们可以使用这些退出码,也可以自己定义。

2.第三种情况

程序崩溃的时候,退出码是没有意义的,一般没有执行retuan语句

3.如何用代码终止一个进程

1.什么是一个正确的终止

首先是return语句,在main中就是终止进程,在其他函数里面是返回上级

还有exit代码在任何时候都作为进程终止。

void print()
  6 {
  7     printf("hello world\n");
  8     exit(0);
  9 }
 10 
 11 int main()
 12 {
 13     print();
 14     printf("hello Linux\n");
 15 
 16     return 0;
 17 }

我们可以看到程序只运行了“hello world”就退出了。

三、进程等待

1.为什么要有进程等待

第一点:如果父进程不管子进程,那么子进程就会处于僵尸状态,会导致内存泄漏。

第二点:父进程要看子进程的状态。

总结的来说就是:父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。

2.如何等待以及等待是什么?

2.wait和waitpid

1.wait

首先我们可以看到此时子进程的状态是僵尸进程,

那么我们在用上wait之后,父进程就会在等待子进程运行结束并且回收,来避免僵尸进程造成的内存泄漏,注意,wait默认是父进程在阻塞等待!

5 int main()
    6 {
    7     pid_t id=fork();
    8     if(id==0)
    9     {
   10         int con=5;
   11         while(con)
   12         {
   13             printf("我是子进程,pid:%d,ppid:%d\n",get      pid(),getppid());
   14             con--;
   15         }
   16         exit(0);
   17     }
   18     else{
   19         printf("我是父进程\n");
W> 20         pid_t ret = wait(NULL);                     
   21
   22     }
   23     return 0;
   24 }

2.waitpid

pid_t waitpid(pid_t pid, int* status, int options);

pid 表示所等待子进程的pid,其中这里的pid有三种状态

  1. 大于0,则是等待指定的进程
  2. 等于0,现在则不用管
  3. 等于-1,则是等待任意一个子进程退出。是任意!等价于wait

status 表示状态,为整型,高16位暂不涉及,低 16 位中,次低 8 位表示退出码,第 7 位表示 core dump,低 7 位表示终止信号。

  1. 如果要获取子进程退出码:status >> 8 & 0xFF。更推荐系统给的宏,WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  2. 如要获取终止信号则为status & 0x7F。更推荐系统给的宏,WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

options 是选项,可以选择父进程是否需要阻塞等待子进程退出

  1. 默认为0:代表阻塞等待,代表着父进程什么都没干,一直在等待子进程退出
  2. WNOHANG:叫做非阻塞等待,非阻塞等待则是父进程接着向下执行,过一会在重复的验证子进程是否退出

3.父进程是怎么拿到这子进程的退出结果的呢?

我们都知道进程具有独立性,进程退出码也是子进程的数据,那么父进程是怎么拿到的呢?

是我们的PCB结构体中保存了任何进程退出是的退出结果信息,本质就是读取了子进程的PCB结构体!而这两个函数是系统调用的,那就是操作系统,是OS把子进程的PCB结构体里的退出信息按照位图的方式放到了status这个数里面!

你可能感兴趣的:(linux,运维,服务器,1024程序员节)