task_struct
task_struct 是linux下管理进程的结构,称为PCB,进程控制块。linux所有的指令本质上都是一个进程。进程 = task_struct + 进程的数据、代码、可执行程序,有属性、有内容。
进程是系统的工作单元。系统由多个进程组成,包括操作系统进程(执行系统代码)、用户进程(执行用户代码)。这些进程都会并发执行,可以在单 CPU 上采用多路复用来实现。
内容 | 描述 |
---|---|
标示符(PID) | 进程的唯一标示符 |
状态 | 状态,退出码,退出信号等 |
优先级 | 进程的优先级 |
程序计数器 | 程序下一条指令的地址(PC指针) |
内存指针 | 程序代码和进程数据的指针,还有和其他进程共享的内存块的指针 |
上下文数据 | 进程执行时处理器的寄存器中的数据 |
I/O状态信息 | 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。 |
记账信息 | 包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。 |
操作系统不直接管理对象,而是管理对象的数据。数据由驱动从硬件获取,反馈给操作系统。如何管理大量的数据?提取对象的数据,用一个数据类型来描述,task_struct。一切皆对象,抽取对象的属性,来描述具体的对象。从管理对象->管理数据->管理数据结构
进程的属性用tast_struct描述并组织,进程对应的代码、数据、资源(可执行程序)同样加载在内存中。
进程的pid和ppid
- pid是每一个进程在系统中的唯一标识符
- ppid是该进程的父进程id
每次在命令行启动进程,操作系统都会为进程分配随机的pid,但是父进程的id是不变的。
为什么?因为命令行上启动的进程的父进程都是bash。
bash进程
Linux下每一个已登录的用户都有bash进程,称为一个会话。当一个用户在终端上面登录后,Linux系统就会给用户提供命令行服务,执行的命令都是这个bash进程的程序替换后的子进程,因此Bash是大部分命令行程序的父进程。
/*
* pointers to (original) parent process, youngest child, younger sibling,
* older sibling, respectively.
*/
struct task_struct *real_parent; /* real parent process */
struct task_struct *parent; /* recipient of SIGCHLD, wait4() reports */
/*
* children/sibling forms the list of my natural children
*/
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
struct task_struct *group_leader; /* threadgroup leader */
进程的状态
操作系统层面
- 运行态:在运行队列中排队的进程称为运行态。不是正在运行,而是表示准备就绪,等待CPU调度。
- 阻塞态:进程不止在申请CPU资源,每一种资源都在操作系统中有自己的等待队列,例如磁盘、网卡等。 当资源没有准备好,操作系统会把CPU的runqueue中的进程交给对应资源的等待队列wait_queue。CPU才可以执行其他的任务。当资源的读写资源就绪才会重新把这个进程放回CPU的运行队列中。类似一边下东西一边看在线视频会很卡,这就是进程阻塞。
- 终止态:进程还在,但是不会再被运行,随时等待被释放。进程终止了,应该把资源如结构体、内存等释放掉。
-
挂起态:挂起的进程不会申请CPU资源。操作系统会把短期内不会被调度(在队列中排的比较靠后)的进程的代码和数据置换到磁盘的swap分区上,而不是在内存里占着,浪费别的进程的资源。例如电脑换了固态硬盘运行会比较流畅。
#swap分区
进程都终止了,为什么不立马释放,而是要维护一个终止态?
因为进程终止后需要释放资源,如关闭文件描述符,回收内存等,这些工作需要一些时间,需要等待操作系统空闲的时候来清理。
Linux系统层面
static const char *task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"T (tracing stop)", /* 8 */
"Z (zombie)", /* 16 */
"X (dead)" /* 32 */
};
状态 | 描述 |
---|---|
R(TASK_RUNNING) | 进程正在执行或准备就绪、等待调度 |
S(TASK_INTERRUPTIBLE) | 当进程等待I/O操作时,会进入可中断的睡眠状态。 |
D(TASK_UNINTERRUPTIBLE) | 当进程等待磁盘操作完成时,会进入不可中断的睡眠状态。 |
T(TASK_STOPPED或TASK_TRACED) | 当进程被暂停或跟踪时,当进程被调试器暂停时,它会进入暂停状态。 |
Z(TASK_DEAD - EXIT_ZOMBIE) | 当子进程已经退出但尚未被父进程回收称为僵尸进程。 |
X(TASK_DEAD - EXIT_DEAD) | 这种情况下,进程即将被销毁。当进程已经退出并且已被父进程回收时,它的状态是 |
运行状态
这个程序在死循环打印,但是进程状态却是S。程序是在死循环,但是代码很少,CPU运行很快,大部分时间都在等待外设就绪,因为显示器作为硬件外设速度太慢了。
状态后面带+表示这是一个前台进程。可以在前台用ctrl+c杀掉进程。
#前台进程
僵尸进程
什么是僵尸进程?
通常情况下,父进程会使用 wait
或 waitpid
函数回收退出之后的子进程的资源,并获得子进程的退出状态。
- 如果子进程先于父进程退出,同时父进程太忙了,无瑕回收子进程的资源,子进程不会直接变为X状态,资源不会立刻被回收。而是变为Z状态,僵尸状态。
为什么有僵尸状态?
在进程执行完之后,要把进程的执行结果通过进程等待返回给父进程或操作系统。以便操作系统或父进程作二次决策。但是父进程还没退出,就需要一直保持这个PCB,等待父进程回收后才能删除。如果父进程完了没有回收资源就退出了,子进程就变成一个孤儿,这时候就被1号进程init领养,由init完成资源的回收工作。
例如main函数的返回值,返回后就给到了PCB中的退出信息。
僵尸进程的危害?
如果父进程一直不回收,那僵尸进程就一直存在,进程是加载到内存中的,需要操作系统维护PCB,就会有内存资源的浪费。
如何杀死僵尸进程?
我们可以在命令行上用kill命令,通过发送信号来杀死一个进程。但是无法杀死一个僵尸进程,即使是kill -9。因为进程已经终止了,但是资源还在进程的PCB中保存着,需要被回收。
但是可以尝试杀掉僵尸进程的父进程,让僵尸进程变成孤儿进程。
孤儿进程
- 如果父进程先于子进程结束,则子进程成为孤儿进程。孤儿进程将被 init (1号)进程领养,并由 init 进程回收该孤儿进程的资源。
但是ps
查看进程状态的时候,父进程没有进入Z状态?
因为命令行上启动的进程的父进程是bash,bash会自动回收他的子进程。
睡眠状态
S(Sleeping)和D(Disk Sleeping)都表示阻塞状态。
- S:浅度睡眠,可中断睡眠,一直在等待资源,可以被唤醒也可以被杀死。
- D:不可中断睡眠,等待磁盘资源。
进程如果等待IO的时候被杀掉,就不能跟资源正确完成交互,可能会造成数据丢失。因此 Linux 引入了D状态,这个状态的进程不会被操作系统杀掉。
要杀死D状态,只能拔电源,如果太多D状态的进程,可能无法关机。只能让进程磁盘交互结束。
进程暂停
进程可以暂停,例如播放音乐和视频,还有类似调试代码的情况。分为stopped和tracing stop。T表示直接暂停,t表示追踪暂停。当进程被调试的时候,遇到断点处的状态。
进程优先级
优先级表明进程获取资源的优先顺序。为什么要排队?系统资源不够。
[chfens@VM-12-8-centos processTest]$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1002 3310 3309 0 80 0 - 29477 do_wai pts/24 00:00:00 bash
0 S 1002 3368 3310 0 80 0 - 1868 do_wai pts/24 00:00:00 scl
0 S 1002 3369 3368 0 80 0 - 28584 do_wai pts/24 00:00:00 bash
0 S 1002 3379 3369 0 80 0 - 29596 do_wai pts/24 00:00:00 bash
0 R 1002 3471 3379 0 80 0 - 38595 - pts/24 00:00:00 ps
pri表示priority,ni表示nice。nice是进程优先级的修正数据。要改优先级得改nice。但是进程的优先级被修改会打破调度器平衡,因此要root才能修改。
但是优先级高的怎么打断低的呢?这可是一个运行队列,只能先进先出?
根据不同的优先级,将指定进程放入不同的队列中。比如1级优先级一条队列,2级进程一条队列。比如每次都是从0级队列开始找,空即找下一个优先级的队列。但实际中是用哈希和位图。
抢占优先级
优先级高的会抢占优先级低的进程,抢占后需要用寄存器保存被抢占进程的上下文信息。
调度复杂度O(1)
进程的特性
独立性
各个进程不会相互干扰。进程本质是内核数据结构+代码和数据。多个进程在系统中运行不等于进程在同时运行。写时拷贝
竞争性
进程存在抢占优先级,低优先级进程会被高优先级的进程打断。调度器会直接把进程从CPU上剥离,让优先级更高的运行。
并行
多个进程在多个CPU下分别同时运行
串行
操作系统大多数是分时的,分时操作系统是基于时间片轮转而调度的。
比如有10个进程,在一段时间段内,给1号进程1ms的时间运行,1ms到了就切换到2号进程,并把原来的1号进程放在运行队列的尾端。
进程的当前路径
a.out用于创建一个log.txt,发现bash在哪个路径下调用程序,就会在哪里创建文件。
查看这个进程的信息,cwd表示current working directory,就是testCode,因为是在testCode下加载的进程,而exe实际是指向别的地方的a.out。
表明进程的当前路径是进程被调用的路径。
fork
本来是单执行流,之后分叉变成两个执行流,形如叉子一样的创建子进程的函数称为fork。表现在操作系统中多了一个PCB,子进程继承自父进程。fork函数之后的父子进程代码共享,但是数据独立。可以通过判断返回值区分父子进程,执行不同功能。文章来源:https://www.toymoban.com/news/detail-508393.html
fork的返回值
fork会给子进程返回0;给父进程返回子进程的pid
为什么有两个返回值?
一个父进程可以有多个子进程,父进程必须能描述各个子进程。因此fork之后给父进程返回子进程的pid,给子进程返回0。
为什么会返回两次?文章来源地址https://www.toymoban.com/news/detail-508393.html
到了这里,关于Linux——进程的概念的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!