进程理解
- (一)查看进程
- (二)创造进程(fork)
-
- [1. 创造的子进程的PCB+代码数据怎么来?](#1. 创造的子进程的PCB+代码数据怎么来?)
- 2.一个函数为什么有两个返回值?
- [3. 为什么这里会有 两个 id值?](#3. 为什么这里会有 两个 id值?)
- 4.一个变量为什么又两个返回值??
- [(三) 进程状态](#(三) 进程状态)
-
- 1.运行状态-R(running)
- 2.浅度睡眠-S(sleeping)
- [3.深度睡眠-D(disk sleep)](#3.深度睡眠-D(disk sleep))
- 4.暂停状态-T(stoped))
- 5.死亡进程-X
- 6.僵尸进程-Z
- 7.孤儿进程
(一)查看进程
- 进程的信息可以通过
/proc
系统文件夹查看
- 大多数进程信息同样可以使用top和ps这些用户级工具来获取
通过系统调用获取进程标示*
- 进程id(PID)
- 父进程id(PPID)
看到下面代码:
运行上面的代码,我们通过如下命令可以看到进程的id:
ps ajx | head -1 && ps ajx | grep proc | grep -v grep
那么这里的父进程2436又是什么呢??
可以看到父进程是一个-bash
,而为什么是bash??
因为在操作命令行的时候,父进程永远是bash外壳,其原理:shell外壳通过创建子进程的方式,以bash的子进程去执行。也就是说当我们输入命令行去执行某项任务时,是bash创造子进程去执行。
(二)创造进程(fork)
- getpid() --- 当前进程id
- getppid() --- 父进程id
看到下面代码:
运行结果如下:
1. 创造的子进程的PCB+代码数据怎么来?
- 子进程内核的数据结构task_struct,也会以父进程的为模板初始化自身。
- 父子进程都会指向同样的代码数据。
那么是不是意味着一个子进程要对数据修改,那么父进程的数据也会同步修改呢??
答案并不是。
因为修改时会发生写时拷贝来维护数据的独立性。(进程是独立的)

2.一个函数为什么有两个返回值?
因为fork函数创造了子进程,相当于父子进程都执行了fork函数,一个进程返回一个值。因此一个函数有两个返回值
3. 为什么这里会有 两个 id值?
- 因为fork创造了一个子进程,而原有的进程和子进程都会返回一个值。
- 子进程的返回值是0,父进程的返回值是大于0。
因此我们可以通过判断id的值,分别在子进程和原本进程添加代码。
子进程先运行还是父进程先运行,是由操作系统的调度器决定的。
这里的判断id值,仅仅是为了分流。
4.一个变量为什么又两个返回值??
进程在运行的时候,具有独立性。父子进程指向共同的代码数据,当fork函数返回不同的值时,变量id会进行写实拷贝,变量id的虚拟地址相同,但真实的物理地址却是不同的。
这里涉及进程地址空间的问题,后面的博客会进行理解
(三) 进程状态
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
下面的状态在kernel源代码里定义:
c
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
- D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
ps aux / ps axj 命令 查看进程状态
1.运行状态-R(running)
- 当一个进程正处于运行状态的时候,并不意味着进程一定处于运行当中,进程可能在运行队列中等或者正在被执行,就叫做运行态,随时可以被CPU调度。
- 一个CPU只有一个运行队列,要被运行的进程会被加载到运行队列当中,每个处于
R状态
的进程会在运行队列中排队等待运行。
实例:
2.浅度睡眠-S(sleeping)
进程正在等待某件事情的完成,处于浅度睡眠状态的进程随时可以被唤醒(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
运行队列满的时候,进程也会是S状态,等待运行队列空出位置。
S状态的进程会被放在等待队列中,等待某种命令结束(也可以scanf函数等)然后再放到运行队列中运行。
- 处于S状态的进程,可以通过
kill
命令来杀死进程,如下:
3.深度睡眠-D(disk sleep)
有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。一个进程处于深度睡眠的状态下,表示该进程不会被杀掉,也就是kill命令都没用,只有等该进程自动唤醒才可以恢复。
什么情况会出现D状态的进程??举个例子:
4.暂停状态-T(stoped)
可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- kill -19 -- 暂停
- kill -18 -- 再次运行
- kill -9 -- 杀死程序

- 某种状态后面带个+(例如S+)
指的是前台进程- 状态后面没有+
指的是后台进程
kill -l命令查看kill相关指令
5.死亡进程-X
杀掉进程,这个状态只是一个返回状态,你不会在任务列表里看到这个状态。杀掉进程是一个瞬时过程,难以查看。
6.僵尸进程-Z
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
进程退出的时候,如果父进程没有主动回收子进程信息,子进程会一直让自己处于Z状态,进程的相关资源尤其是task_struct结构体不能被释放。

命令行脚本:
僵尸进程危害
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
- 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!存在内存泄漏的问题!
7.孤儿进程
- 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
- 父进程先退出,子进程就称之为"孤儿进程"
- 孤儿进程被1号init进程领养,当然要有init进程回收。
为什么要被领养??因为孤儿进程未来也需要被释放。