进程的概念和进程控制
这里将介绍进程的基本概念,什么是进程,如何描述和组织进程,接着讨论进程的状态,最后介绍进程 控制
进程概念
1. 概念:
a. 进程是程序的一次动态执行过程
b. 担当分配系统资源(CPU时间、内存)的实体。(从内存角度)
2. 描述进程---PCB
进程的信息被放在一个叫做进程控制块的数据结构中,在Linux中描述进程的结构体叫做task_struct。task_struct是Linux内核的一种数据结构,它会被装载到内存里面包含着进程的信息。
task_struct内容分类:
- 标识符:描述本进程的唯一标示符,用来区分其他进程
- 状态:任务状态,退出代码,退出信号
- 优先级:相对于其他进程
- 程序计数器:程序中即将被执行的下一条指令的地址
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
- 上下文数据:进程执行时处理器的寄存器中的数据
- I/O状态信息:包括显示的I/O请求,分配给进程I/O设备和被进程使用的文件列表
- 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
3. 组织进程
所有运行在系统里的进程都以task_struct链表的形式存在内核中。
注:创建一个进程需要做哪些事情?
1. 要创建一个进程,必须把这个程序的代码和数据加载到内存,操作系统必须为该进程创建对应的task_struct.
2. 再用双链表将其组织起来 。
进程的状态
除了以上提到的几个状态外,这里重点介绍下:Z-僵尸进程和孤儿进程
僵尸进程
僵尸进程是一个比较特殊的状态,产生僵尸进程的原因是子进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程。
- 模拟僵尸进程的场景
eg:创建维持30s的僵尸进程的例子。
一个终端运行,一个终端进程监视
看到结果:
- 僵尸进程的危害
1. 父进程如果一直不读取,则进程一直处于僵尸状态维护退出状态本身就需要数据维护,所以会存在资源的浪费。
2. 内存泄露。
孤儿进程
父进程退出,子进程就称之为“孤儿进程” 。 孤儿进程被1号init进程领养,当然要有init进程回收。
- 模拟僵尸进程的场景
程序:
运行程序&结果查看
进程控制
进程创建
fork函数
fork函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
创建进程的一般工作
- 分配一个PCB,拷贝父进程的PCB的绝大部分数据
- 给子进程分配资源
- 复制父进程地址空间的数据
- 将进程状态置为就绪态,插入就绪队列
fork函数的返回值
子进程返回0,父进程返回的是子进程的pid。
eg: 当一个进程调度fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都可以开始它们自己的旅程 ,如下进程:
为什么会输出这样的消息呢?
因为fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注:fork之后,谁先执行完全由调度器决定。
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。但一般情况下,一个父进程希望复制自己,是父子进程同时执行不同的 代码段。(子进程从fork函数返回后,调用exec函数)
fork调用失败的原因
- 系统中有太多的进程(内存不够,资源不够)
- 实际用户的进程数超过了限制
vfork函数
- vfork函数也是用来创建子进程的。
- vfork用于创建一个子进程,则子进程和父进程共享地址空间,fork的子进程具有独立地址空间
- vfork保证子进程先运行,在它调用exec或(exit)之后父进程才可能被调度运行
进程终止
进程的退出场景
- 代码运行完毕,结果正确
- 代码运行完毕,,结果不正确
- 代码异常终止
进程常见的退出方法
正常退出
- 用main退出
- 调用exit
- _exit
异常退出
- ctrl+c ,信号终止
_exit与exit的区别
进程终止时,_exit是强制终止的。
exit会执行用户通过atexit或on_exit定义的清理函数,关闭所用打开的流,所有的缓存数据均被写入,调用_exit.
进程等待
之前说的僵尸进程,就是子进程退出时,父进程不管不顾。进程一旦进入僵尸状态,那就刀枪不入,连kill -9也无能为力。所以父进程需要通过进程等待的方式,回收子进程资源,获取子进程退出信息。
进程等待的必要性
回收僵尸进程,避免内存泄漏,保证子进程优先退出,父进程最后退出,父进程要获得子进程的退出信息,从而保证后序的机制
进程等待的方式
wait和waitpid的区别:
wait会令调用者阻塞直到某一个子进程退出
waitpid则可以通过设置一个选项(options)来设置为非阻塞,另外waitpid并不是等待第一个结束的进程,而是等待参数中pid指定的进程