【Linux】-----进程第一弹
目录
概念
课本上对于进程的概念是这样子说的:一个正在执行的程序就叫做进程。
对于操作系统的内核是这样子说的:担当分配系统资源(CPU时间,内存)的实体!(很抽象)
什么叫做分配系统资源的实体?这得借助冯诺依曼体系结构来看看
从冯诺依曼结构可以看出,能与CPU直接打交道的是存储器,也就是的内存! 实际上我们平时所创建的文件都是放在外设磁盘上的。一个可执行程序的本质就是一个二进制文件,在我们没有运行这个可执行程序时,实际上它是被放在磁盘中的。当我们将这个可执行程序运行起来的时候,本质就是将这个可执行程序加载到内存中,只有当程序被加载到内存中的,CPU才能进行访问,此时这个可执行程序就分配到CPU的资源,这个就叫做一个实体,但是需要注意严格意义上来说还不能叫进程。只能说是进程的一部分。将可执行程序加载到内存,实际上是将对应代码和数据加载到了内存。
而内存会同时存在多个可执行程序的代码和数据,到底应该先执行哪一部分的?以及其他部分的数据执行了多久?等一系列进程的信息操作系统该如何进行管理呢?
我们都知道操作系统实际上是一款软件,对下能够进行软硬件管理,对上能提供安全稳定高效的运行环境的软件,这就包括了对进程的管理工作。。。。。仅需记住操作系统管理的六字真言:先描述,再组织!
先把进程的信息采用结构体的方式描述起来,再使用链表或者更高效的数据结构将结构体组织起来!
ps:实际上开机第一个运行的可执行程序就是操作系统,将操作系统的代码和数据先加载到内存中。。
描述进程-PCB
PCB(process control block),进程控制块。是一个结构体,这个结构体里面存放着对应进程的信息,或者说是进程的所有属性信息。一个进程都有一个对应的PCB。
操作系统对进程的管理本质就是对进程信息的管理,每当新形成一个进程时,操作系统便会将其描述起来,在通过链表的形式组织的。此时对进程的操作,就是对链表完成增删查改的操作!
从上图就可以给进程下一个定义:进程=PCB+对应的代码和数据!
在Linux操作系统中下的PCB称为:task_struct,是PCB的一种!
如何理解进程的动态运行?
调度运行进程的本质就是让进程控制块task_struct进行排队,只要task_struct在不同的队列里,进程就可以访问不同的资源,这就是动态运行!!!
task_struct中主要包含以下信息:
- 标识符:pid/ppid
- 状态:任务状态,退出码等
- 优先级:相对于其他进程的优先级
- 内存指针
- 上下文数据
- I/O状态信息
查看进程
指令:ps axj
功能:查看当前系统中所有的进程
若想要查看指定的进程,可以加上管道 | 和grep 特定的进程
指令:ps ajx | grep 指定进程名称
获取进程标识符
- pid:进程的标识符
- ppid:当前进程的父进程的pid
这两个属性都是存在于内核task_struct,用户不能直接访问,只能采用系统提供的接口调用。
- getpid():获取进程id
- getppid():获取父进程id
实例:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 int main()
5 {
6 while(1)
7 {
8 printf("my process,my pid is:%d,my ppid is:%d \n",getpid(),getppid());
9 sleep(1);
10 }
11 return 0;
12 }
注意:每次重新运行进程时,对应的pid都不一样。
终止进程
两种指令:
- ctrl+C
- kill -9 进程的pid
fork创建进程
fork是一个由OS提供的函数,功能就是创建一个子进程。
例如:
可以看到这里的结果打印的是两句“after fork”,因为fork之后,父子进程代码共享,也就是创建出来的子进程和父进程执行相同的代码!
注意:
- 创建一个进程本质就是系统多出了一个进程,也就是多出来了一个PCB+代码和数据
- 父进程的代码和数据是从磁盘加载来的,而默认情况下,子进程会继承父进程的PCB
- 父子进程之间仅是代码共享,数据并不共享,有各自的存储数据空间(写时拷贝)
为什么要创建子进程?
- 实际上就是因为想让多个进程并发的跑起来,每个进程都做不一样的事情!
这时就需要采用if语句分流了!
返回值说明
上图可以看到fork实际上是有返回值的,类型为pid_t;
返回值=0 | 表示子进程创建成功,返回子进程的pid给父进程,在子进程中返回0 |
返回值>0 | 表示父进程,接收的是子进程的pid |
返回值<0 | 表示进程创建失败 |
例如:
可以看到采用if分流就能实现父子进程都执行各自模块的代码,做着不一样的事情!
注意:fork会返回两次,因为fork后的代码父子共享,子进程已经存在了,子进程返回一次,父进程又返回一次!
至于为啥同一个id会存在两个不同的返回值,那就涉及到虚拟地址空间+父子写时拷贝的问题了(后续提及)
进程的状态
进程的状态属于task_struct里的一个属性。一个进程可以有几个状态,更改进程状态实际就是更改task_struct内部的一个属性。。。下面具体来看Linux下的进程状态。。
①运行状态(R)
并不意味着进程一定是在运行,它可能表示的是进程正在运行或者处于运行队列中。
例如:
查看一下上述进程的状态,可以采用下面这条指令:
ps ajx | head -1 && ps ajx | grep 可执行程序名称
可以看到当前进程就是处于运行状态,R+表示在前台运行,R表示在后台运行
②浅度睡眠(S)
这种状态一般都是进程在等待某种资源就绪,也称为可中断睡眠状态(能之间终止掉睡眠状态)
例如:死循环打印一段代码
上述处于睡眠状态因为CPU运行速度很快,早早就将程序执行完成,大部分的时间都在等待显示器资源就绪!
③深度睡眠(D)
一个处于D(disk sleep)状态的进程,是不可被杀掉的,操作系统也不能将其杀掉,也叫不可中断睡眠状态。
为何会存在D状态?
主要是为了防止数据丢失问题!当一个进程在向着磁盘写入信息时,若操作系统直接将该进程杀死,此时如果磁盘正在读取数据,但该进程已经被杀掉,那么磁盘去哪读数据?这就造成了数据丢失的问题。
所以一个进程若正在进行数据的写入操作时,会将自身的状态设置为D状态!
注意:要想结束掉处于这种状态的进程,只能等进程自己醒来,或者断电重启操作系统1
④暂停状态(T)
- 暂停:kill -19 对应进程的pid
- 恢复进程之前状态:kill -18 对应的进程pid
恢复后的进程全部在后台运行 !看到这里或许会疑惑,这个18,19为什么能这样做?实际上就是kill指令存在一堆信号,相当于宏定义那样。
可见,我们实际上也可以采用kill + SIGSTOP来暂停进程!但这样没有数字来得简单!
⑤僵尸状态(Z)(重点)
处于这种状态的进程称为僵尸进程!
是什么?
一个已经运行完毕的进程,不会立刻退出,而是需要维持自己的退出信息,在自己的进程task_struct会记录自己的退出信息,这个退出信息未来会让父进程或者操作系统进行读取,只有当父进程读取退出信息,子进程之前申请的资源才能释放掉,若退出信息一直未被读取,相关资源就不会释放。
一个正在等待自己退出信息被读取的进程,称为僵尸进程,此时也称为该进程处于僵尸状态。僵尸进程无法将其kill掉。
举例
while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v grep; sleep 1; done
使用上述监视进程的脚本语言来观察观察。。
可以看出,当5s后子进程运行结束了, 但是在并没有退出,而是在等待父进程回收,此时该子进程的状态就变为僵尸状态!
ps:
- 直接在命令行中启动的进程,不会存在僵尸状态,因为在命令行中启动的进程的父进程是(bash)系统,运行结束后会自动回收!
- 至于程序中的进程如何读取退出信息,会使用到wait/waitpid系统接口,这里先不细说!
危害
- 若父进程一直不读取子进程的退出信息,那么子进程会一直处于僵尸状态。。
- 如果一个父进程存在多个子进程,但是都不回收,必然造成资源的浪费。。进程的PCB是占用内存空间的。
- 僵尸进程数量越多,实际可利用资源就越少,那就会造成内存泄漏的问题!
孤儿进程
所谓孤儿进程,顾名思义,就是没有父亲呗。父进程先于子进程退出,子进程称为孤儿进程,运行在后台,父进程成为1号进程(OS本身)。
孤儿进程的退出,会被1号进程负责任的进行处理,因此不会成为僵尸进程!
演示
不难发现,孤儿进程的ppid变成了1,也就是被系统领养了(爷爷)!
⑥死亡状态(X)
实际就是当一个进程运行结束并且被父进程回收时,此时的进程就是完全退出了,也叫做死亡状态,我们肯定看不到的!
今天的分享就到这,希望对你有用!
原文地址:https://blog.csdn.net/m0_73470633/article/details/142219320
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!