(五)进程控制

目录

1. 有关进程

1.1 什么是进程

1.2进程ID(PID)

1.3 三个特殊的进程

进程 ID == 0 的进程

进程ID == 1的进程

进程ID == 2的进程

1.4获取与进程相关的各种ID的函数

2. 程序的运行过程

2.1 程序如何运行起来

2.2 fork

2.2.1 函数原型

2.2.2复制的原理

2.2.3 父子进程各自会执行哪些代码     

2.3 父子进程共享操作文件

(1)情况1:独立打开文件

(2)情况2:fork之前打开文件

2.4 子进程会继承父进程的哪些属性

2.4.1 子进程继承如下性质

2.4.2 子进程独立的属性

3. exec加载器

3.1 exec的作用

3.2 exec函数族

3.2.1 execve函数原型

3.2.2 exec的作用

3.2.3 在命令行执行./a.out,程序是如何运行起来的

3.2.4 双击快捷图标,程序是怎么运行起来的

4. system函数

5. 回收进程资源

5.1 为什么要回收进程的资源?

5.2 由谁来回收进程资源

5.3 僵尸进程和孤儿进程

5.3.1 僵尸进程

5.3.2 孤儿进程

6. wait函数

6.1 进程的终止

6.1.1 正常终止

6.1.2 异常终止

6.1.3 进程终止状态

6.2 父进程如何从内核获取子终止状态

6.2.1 如何获取

6.2.2 wait函数原型

6.2.3 从进程终止状态中提取进程终止的原因、返回值或者信号编号

6. 进程状态

7. java进程

8.进程关系

8.1 父子关系

8.2 进程组

8.3 会话期关系

9. 守护进程


1. 有关进程

1.1 什么是进程

1.2进程ID(PID)

OS为了能够更好地管理进程,为每个进程分配了一个唯一的编号(非负整数),这个编号就是PID

  • 如果当前进程结束了,这个PID可以被可以被重复使用,但是所有“活着”的进程,它们的进程ID一定都是唯一的。
  • 因为ID唯一性,当我们想创建一个名字唯一的文件时,往往可以在文件名中加入PID,这样就能保证文件名唯一性。

1.3 三个特殊的进程

0、1、2这个三个进程,是OS启动起来后会一直默默运行的进程,直到关机OS结束运行

进程 ID == 0 的进程

进程ID == 1的进程

进程ID == 2的进程

1.4获取与进程相关的各种ID的函数

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);
pid_t getppid(void);
uid_t getuid(void);
gid_t getgid(void);

  

2. 程序的运行过程

2.1 程序如何运行起来

(1)在内存中划出一片内存空间
(2)将硬盘上可执行文件中的代码(机器指令)拷贝到划出的内存空间中
(3)pc指向第一条指令,cpu取指运行

在Linux下,OS提供两个非常关键的API,一个是fork,另一个是exec。

    fork  :开辟出一块内存空间
    exec:将程序代码(机器指令)拷贝到开辟的内存空间中,并让pc指向第一条指令,CPU开始执行,进程就运行起来了
              运行起来的进程会与其它的进程切换着并发运行。

 

2.2 fork

2.2.1 函数原型

#include <unistd.h>

pid_t fork(void);

  

 

2.2.2复制的原理

Linux有虚拟内存机制,所以父进程是运行在虚拟内存上的,虚拟内存是OS通过数据结构基于物理内存模拟出来的,因此底层的对应的还是物理内存

复制时子进程时,会复制父进程的虚拟内存数据结构,那么就得到了子进程的虚拟内存,相应的底层会对应着一片新的物理内存空间,里面放了与父进程一模一样代码和数据,

2.2.3 父子进程各自会执行哪些代码     

 

 

验证子进程复制了父进程的代码和数据

      

      

      printf("befor fork"); 在父子进程中都输出   因为没加换行符 所以 在子进程中遇见换行缓冲区被刷新到屏幕

       

2.3 父子进程共享操作文件

(1)情况1:独立打开文件

 

独立打开同一文件时,父子进程各自的文件描述符,指向的是不同的文件表。

因为拥有不同的文件表,所以他们拥有各自独立的文件读写位置,会出现相互覆盖情况

如果不想相互覆盖需要加O_APPEND标志。

(2)情况2:fork之前打开文件

子进程会继承父进程已经打开的文件描述符,

          如果父进程的3描述符指向了某个文件,子进程所继承的文件描述符3也会指向这个文件

由于共享的是相同的文件表,所以拥有共同的文件读写位置,不会出现覆盖的情况。

子进程的0 1 2这三个打开的文件描述符,其实也是从父进程那里继承过来的,并不是子进程自己去打开的

         同样的父进程的 1 2又是从它的父进程那里继承过来的,最根溯源的话,都是从最原始的进程哪里继承过来的

2.4 子进程会继承父进程的哪些属性

2.4.1 子进程继承如下性质

  • (1)用户ID,用户组ID
  • (2)进程组ID
  • (3)会话期ID
  • (4)控制终端
  • (5)当前工作目录
  • (6)根目录
  • (7)文件创建方式屏蔽字
  • (8)环境变量
  • (9)打开的文件描述符
  •        等等

2.4.2 子进程独立的属性

  • (1)进程ID。
  • (2)不同的父进程ID。
  • (3)父进程设置的锁,子进程不能被继承。
  •      等等

3. exec加载器

3.1 exec的作用

父进程fork复制出子进程的内存空间后,子进程内存空间的代码和数据和父进程是相同的,这样没有太大的意义,我们需要在子进 程空间里面运行全新的代码,这样才有意义。

有了exec后,我们可以单独的另写一个程序,将其编译好后,使用exec来加载即可。

3.2 exec函数族

exec的函数有很多个,它们分别是execve、execl、execv、execle、execlp、execvp,都是加载函数。

其中execve是系统函数,其它的execl、execv、execle、execlp、execvp都是基于execve封装得到的库函数,

3.2.1 execve函数原型

#include <unistd.h>

int execve(const char *filename, char **const argv, char **const envp);

  

 

3.2.2 exec的作用

将新程序代码加载(拷贝)到子进程的内存空间,替换掉原有的与父进程一模一样的代码和数据,让子进程空间运行全新的程序。

3.2.3 在命令行执行./a.out,程序是如何运行起来的

(1)窗口进程先fork出子进程空间
(2)调用exec函数加载./a.out程序,并把命令行参数和环境变量表传递给新程序的main函数的形参

3.2.4 双击快捷图标,程序是怎么运行起来的

(1)图形界面进程fork出子进程空间
(2)调用exec函数,加载快捷图标所指向程序的代码
            以图形界面方式运行时,就没有命令行参数了,但是会传递环境变量表

4. system函数

如果我们需要创建一个进子进程,让子进程运行另一个程序的话,可以自己fork、execve来实现,但是这样的操作很麻烦

所以就有了system这个库函数,这函数封装了fork和execve函数

调用时会自动的创建子进程空间,并把新程序的代码加载到子进程空间中,然后运行起来。

#include <stdlib.h>
int system(const char *command);

(1)功能:创建子进程,并加载新程序到子进程空间,运行起来。

(2)参数:新程序的路径名
 

5. 回收进程资源

进程运行终止后,不管进程是正常终止还是异常终止的,必须回收进程所占用的资源。

5.1 为什么要回收进程的资源?

(1)程序代码在内存中动态运行起来后,才有了进程,进程既然结束了,就需要将代码占用的内存空间让出来(释放)。

(2)OS为了管理进程,为每个进程在内存中开辟了一个task_stuct结构体变量,进程结束了,那么这个结构体所占用的内存空间也需要被释放。

(3)等其它资源                    

5.2 由谁来回收进程资源

由父进程来回收,父进程运行结束时,会负责释放子进程资源。

  • R 正在运行
  • S 处于休眠状态
  • Z 僵尸进程,进程运行完了,等待被回收资源

5.3 僵尸进程和孤儿进程

ps查看到的进程状态

5.3.1 僵尸进程

子进程终止了,但是父进程还活着,父进程在没有回收子进程资源之前,子进程就是僵尸进程

 

为什么子进程会变成僵尸进程?

子进程已经终止不再运行,但是父进程还在运行,它没有释放子进程占用的资源,所以就变成了占着不拉屎僵尸进程。

 

就好比人死后不腐烂,身体占用的资源得不到回收是一样的,像这种情况就是所谓的僵尸。

5.3.2 孤儿进程

没爹没妈的孩子就是孤儿,子进程活着,但是父进程终止了,子进程就是孤儿进程。

 

为了能够回收孤进程终止后的资源,孤儿进程会被托管给我们前面介绍的pid==1的init进程

每当被托管的子进程终止时,init会立即主动回收孤儿进程资源

回收资源的速度很快所以孤儿进程没有变成僵尸进程的机会。

6. wait函数

作用:父进程调用这个函数的功能有两个

  • (1)主动获取子进程的“进程终止状态”。
  • (2)主动回收子进程终止后所占用的资源。

wait函数,在实际开发中用的很少

6.1 进程的终止

6.1.1 正常终止

(1)main调用return
(2)任意位置调用exit
(3)任意位置调用_exit
不管哪种方式来正常终止,最终都是通过_exit返回到OS内核的。

6.1.2 异常终止

如果是被某个信号终止的,就是异常终止。

(1)自杀:自己调用abort函数,自己给自己发一个SIGABRT信号将自己杀死。
(2)他杀:由别人发一个信号,将其杀死。

6.1.3 进程终止状态

(1)退出状态与“进程终止状态”

return、exit、_exit的返回值称为“进程终止状态”,严格来说应该叫“退出状态”,
return(退出状态)、exit(退出状态)或_exit(退出状态)

当退出状态被_exit函数交给OS内核,OS对其进行加工之后得到的才是“进程终止状态”,父进程调用wait函数便可以得到这个“进程终止状态”。

                                   

(2)OS是怎么加工的?

1)正常终止

          进程终止状态 = 终止原因(正常终止)<< 8 | 退出状态的低8位

2)异常终止

          进程终止状态 = 是否产生core文件位 | 终止原因(异常终止)<< 8 | 终止该进程的信号编号

(3)父进程调用wait函数,得到“进程终止状态”有什么用

父进程得到进程终止状态后,就可以判断子进程终止的原因是什么,如果是正常终止的,可以提取出返回值,如果是异常终止的,可以提取出异常终止进程的信号编号。

 

当有OS支持时,进程return、exit、_exit正常终止时,所返回的返回值(退出状态),最终通过“进程终止状态”返回给了父进程。

 

这有什么用,比如,父进程可以根据子进程的终止状态来判断子进程的终止原因,返回值等等,以决定是否重新启动子进程, 或则做一些其它的操作,不过一般来说,子进程的终止状态对父进程并没有太大意义。

6.2 父进程如何从内核获取子终止状态

6.2.1 如何获取

(1)父进程调用wait等子进程结束,如果子进程没有结束的话,父进程调用wait时会一直休眠的等(或者说阻塞的等)。

(2)子进程终止返回内核,内核构建“进程终止状态”

           

(3)内核向父进程发送SIGCHLD信号,通知父进程子进程结束了,你可以获取子进程的“进程终止状态”了。

           

6.2.2 wait函数原型

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

(1)功能:获取子进程的终止状态,主动释放子进程占用的资源
        
(2)参数:用于存放“进程终止状态”的缓存

(3)返回值:成功返回子进程的PID,失败返回-1,errno被设置。
 

6.2.3 从进程终止状态中提取进程终止的原因、返回值或者信号编号

(1)进程状态中所包含的信息

(2)如何提取里面的信息

系统提供了相应的带参宏,使用这个带参宏就可以从“进程终止状态”中提取出我们要的信息。
    
提取原理:相应屏蔽字&进程终止状态,屏蔽掉不需要的内容,留下的就是你要的信息。        


(3)wait的缺点

如果父进程fork创建出了好多子进程,wait只能获取最先终止的那个子进程的“终止”状态,其它的将无法获取,

如果你想获取所有子进程终止状态,或者只想获取指定子进程的进程终止状态,需要使用wait的兄弟函数waitpid,

它们的原理是相似的

6. 进程状态

每个进程与其它进程并发运行时,该进程会在不同的“进程状态”之间进行转换

7. java进程

 如何运行编译型和解释型语言的程序

(1)java程序的运行

1)父进程(命令行窗口、图形界面)会fork复制出子进程空间
2)调用exec加载java虚拟机程序,将虚拟机程序的代码拷贝到子进程空间中


其实最简单的理解就是,java虚拟机就代表了java进程。

 

当你运行另一个java程序时,又会自动地启动一个虚拟机程序来解释java字节码,此时另一个java进程又诞生了。

也就是说你执行多少个java进程,就会运行多少个java虚拟机,当然java虚拟机程序在硬盘上只有一份,只不过被多次启动而已。

(2)java虚拟机怎么得到

当我们运行java程序时,虚拟机会被自动启动。

 虚拟机一般是运行在OS上的,不过其实虚拟机也可以运行在没有OS的裸机上

(3)在java程序里面,也可以调用java库提供的类似的fork和exec函数,我们自己来创建一个java子进程,并执行新程

java库提供的类似的fork、exec函数,下层也是调用OS的fork、exec函数。

8.进程关系

进程间的关系,大致有三种,即父子关系、进程组关系、会话期关系。

8.1 父子关系

已有进程调用fork创建出一个新的进程,那么这两个进程之间就是父子进程关系,子进程会继承和父进程的属性。

8.2 进程组

8.2.1 什么是进程组

   多个进程可以在一起组成一个进程组,其中某个进程会担任组长,组长进程的pid就是整个进程组的组ID。

8.2.2 进程组的生命周期

   就算进程组的组长终止了,只要进程中还有一个进程存在,这个进程组就存在。

8.3 会话期关系

多个进程组在一起,就组成了会话期。

9. 守护进程

守护进程也被称为精灵进程。

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值