2024-09-22 进程优先级,进程切换

一、僵尸状态 & 孤儿进程

进程退出:内核数据结构(task_struct 维护)+ 代码和数据(直接释放)

  1. 代码不会执行了
    首先可以立即释放的就是进程对应的程序信息数据。
  2. 进程退出要有退出信息(进程的退出码),其保存在自己的task_struct的内部。
    结构体 ➡️ 成员属性 ➡️ 退出信息(int code, 其他)
  3. 管理结构task_struct必须被OS维护起来,方便用户未来获取进程退出的信息。

先创建内核数据机构再加载代码和数据,即使代码和数据没加载进来,创建内核数据结构后进程即被创建,只是不会被调度。

#include <stdio.h>
#include <unistd.h>

int main(){
	printf("父进程运行:pid = %d,ppid = %d\n", getpid(), getppid());
	pid_t id = fork();
	if(id == 0){
		//子进程
		int cnt = 10;
		while(cnt){
			printf("循环第%d次,子进程对应的 pid = %d, ppid = %d\n", 11 - cnt, getpid(), getppid());
			sleep(2);
			--cnt;
		}
	}
	else{
		//父进程
		while(1){
			printf("父进程对应的 pid = %d\n", getpid());
			sleep(1);
		}
	}
	return 0;
}

通过如下指令查看进程状态:
while :; do ps ajx | head -1; ps ajx | grep myproc; sleep 1; done

   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 617150  617285  617285  617123 pts/1     617285 S+    1000   0:00 ./myproc
 617285  617286  617285  617123 pts/1     617285 S+    1000   0:00 ./myproc
 617270  617361  617360  617253 pts/2     617360 S+    1000   0:00 grep --color=auto myproc
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 617150  617285  617285  617123 pts/1     617285 S+    1000   0:00 ./myproc
 617285  617286  617285  617123 pts/1     617285 Z+    1000   0:00 [myproc] <defunct>
 617270  617366  617365  617253 pts/2     617365 S+    1000   0:00 grep --color=auto myproc

发现子进程进入僵尸状态(Z:维护自己的task_struct,方便未来父进程读取退出状态)。
如果没人管,就会一直维持僵尸状态(task_struct一直存在会消耗内存,造成内存泄漏!)。
默认没人管怎么处理?一般需要父进程读取子进程信息,子进程才会自动退出。

语言层面的内存泄漏问题,如果在常驻内存的进程中出现,影响比较大。

父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程”,孤儿进程被1号 initdsystemd 进程领养、回收。


二、【总结】Linux下的进程状态及其转换过程

1. 进程状态的分类

在Linux中,进程通常会处于以下几种状态之一:

  • R (Running): 运行态,表示进程正在运行或准备运行。
  • S (Sleeping): 睡眠态,分为两种:
    • 可中断睡眠态(Interruptible Sleep,S):进程正在等待某个事件或资源,收到信号后可被唤醒。
    • 不可中断睡眠态(Uninterruptible Sleep,D):进程正等待设备I/O等系统资源,不能被信号中断唤醒。
  • T (Stopped): 停止态,进程被暂停执行,如收到 SIGSTOP 信号或在调试过程中被暂停。
  • Z (Zombie): 僵尸态,进程已经终止,但其父进程尚未调用 wait() 系统调用来获取它的退出状态。
  • X (Dead): 已终止,进程结束且系统已清理其所有资源。

2. 进程状态转换过程

(1)创建进程
  • 初始状态:R(Running)
    • 当调用 fork()clone() 系统调用时,一个新的子进程会被创建。新进程最初处于运行态(R),并继承父进程的资源和执行环境。
(2)运行态和睡眠态的转换
  • R → S(可中断睡眠态)

    • 进程在等待某些资源或事件时会进入可中断睡眠态。比如,当进程请求磁盘I/O或等待网络数据时,它会调用 sleep()wait() 系统调用,导致状态从运行态转换为睡眠态。
    • 进程在此状态下可以被信号唤醒。
  • S(可中断睡眠态)→ R(Running)

    • 当进程等待的事件发生时(如I/O操作完成),或当它收到信号时,进程将被唤醒,状态转换为运行态。
  • R → D(不可中断睡眠态)

    • 进程在等待设备硬件响应或内核中的某些特定事件时,可能进入不可中断睡眠态(如等待磁盘I/O)。这种状态下的进程不能被信号唤醒。
  • D → R(Running)

    • 当设备操作或资源可用时,不可中断睡眠态的进程会被内核唤醒,并返回运行态。
(3)运行态和停止态的转换
  • R → T(Stopped)

    • 进程接收到 SIGSTOPSIGTSTP 信号时会暂停执行,进入停止态。调试过程中,进程被暂停也会处于此状态。
  • T → R(Running)

    • 进程收到 SIGCONT 信号后可以恢复运行,状态从停止态转换为运行态。
(4)僵尸进程的产生及清理
  • R → Z(Zombie)

    • 当进程完成执行并调用 exit() 函数后,它的状态会变为僵尸态。进程已经终止,但它的进程表条目依然存在,直到父进程通过 wait()waitpid() 系统调用收集其退出状态。
  • Z → X(Dead)

    • 当父进程通过 wait()waitpid() 获取到僵尸进程的退出状态后,内核会释放僵尸进程的所有资源,进程真正终止。

3. 进程状态的影响因素

进程状态的转换主要受以下因素影响:

  • 系统资源:进程可能因为等待系统资源(如I/O、内存、锁)而进入睡眠态。
  • 调度器:进程何时运行取决于调度器的策略,如时间片的分配、优先级等。
  • 信号:进程可以通过信号进入停止态或被唤醒等。

三、进程优先级

1. 是什么?获得某种资源的先后顺序

例:排队的本质就是为了确认优先级,资源从打饭的窗口获取

2. 为什么?

因为目标资源比较少

权限:能不能的问题;优先级:已经能了,谁先谁后的问题

3. 怎么办?

task_struct ➡️ 优先级属性 ➡️ 几个特定的int类型变量来表示优先级
优先级数字越小代表优先级越高

用指令 ps -la

F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S     0  618246  618229  0  80   0 -  2585 do_wai pts/1    00:00:00 su
4 S  1000  618247  618246  0  80   0 -  2225 do_wai pts/1    00:00:00 bash
0 S  1000  619020  618247  1  80   0 -   694 wait_w pts/1    00:00:00 prio
4 R     0  619021  618979  0  80   0 -  2518 -      pts/2    00:00:00 ps

PRI:Linux进程的优先级
NI:优先级的nice数据
最终优先级 = pri(默认/旧的)+ NI(优先级的修正数据)

UID即用户ID,揭示了进程是由谁启动的

  1. 文件会记录拥有者、所属组和对应的权限
  2. Linux 中一切皆文件
  3. 所有操作都是进程操作,进程会记录其启动者

    1.3.比对就是权限的基本控制原则

进程竞争的是CPU资源


3.1 修改优先级(非高频操作且不建议修改)

top命令 ➡️ r ➡️ 按照提醒修改即可(注意OS禁止频繁修改和没有权限修改)

$ ps -al
F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S     0  621113  621089  0  80   0 -  2585 -      pts/1    00:00:00 su
4 S  1000  621114  621113  0  80   0 -  2199 do_wai pts/1    00:00:00 bash
4 S     0  621186  621170  0  80   0 -  2585 -      pts/2    00:00:00 su
4 S  1000  621187  621186  0  80   0 -  2199 do_wai pts/2    00:00:00 bash
0 S  1000  621239  621114  0  80   0 -   694 hrtime pts/1    00:00:00 pri
0 R  1000  621240  621187  0  80   0 -  2518 -      pts/2    00:00:00 ps

如果设定nice值为100

F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S     0  621113  621089  0  80   0 -  2585 -      pts/1    00:00:00 su
4 S  1000  621114  621113  0  80   0 -  2199 do_wai pts/1    00:00:00 bash
4 S     0  621186  621170  0  80   0 -  2585 -      pts/2    00:00:00 su
4 S  1000  621187  621186  0  80   0 -  2199 do_wai pts/2    00:00:00 bash
0 S  1000  621239  621114  0  99  19 -   694 hrtime pts/1    00:00:00 pri
0 R  1000  621245  621187  0  80   0 -  2518 -      pts/2    00:00:00 ps

结果说明 nice值最大为19


如果设定nice值为-100

root@hcss-ecs-2ff4:/home/usr1/mylinux/F0922# ps -al
F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S     0  621113  621089  0  80   0 -  2585 do_wai pts/1    00:00:00 su
4 S  1000  621114  621113  0  80   0 -  2199 do_wai pts/1    00:00:00 bash
4 S     0  621186  621170  0  80   0 -  2585 do_wai pts/2    00:00:00 su
4 S  1000  621187  621186  0  80   0 -  2199 do_wai pts/2    00:00:00 bash
0 S  1000  621247  621114  0  60 -20 -   694 hrtime pts/1    00:00:00 pri
4 S     0  621254  621187  0  80   0 -  2649 do_wai pts/2    00:00:00 su
4 S     0  621255  621254  0  80   0 -  1909 do_wai pts/2    00:00:00 bash
4 R     0  621263  621255  0  80   0 -  2518 -      pts/2    00:00:00 ps

结果说明 nice值最小为20


NI取值范围为[-20, 19]
NI在可控范围内:因为分时操作系统在进行进层调度时要做到尽可能公平
pri(最终)= pri(default 80)+ nice 修改实质上是重置

  • 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行(任何时刻)
  • 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
    (在一个时间段内,多个进程代码在“同时”推进)

四、进程切换

1. 引入

(1)每个进程都有时间片,时间片到了,进程就该被切换
(2)Linux是基于时间片进行调度轮转的
(3)一个进程在时间片到了的时候并不一定跑完的,可在任何地方被重新调度切换

2. 进程切换

  • 保存上下文数据:保留历史运行痕迹
  • 恢复上下文数据:保留不是目的,而是手段,未来恢复才是目的

(1)进程在运行时会有很多的临时数据都在CPU的寄存器中保存
CPU怎么知道读到哪一行代码了?
eip(pc):当前执行指令的下一条指令的地址(pc = 当前地址 + 读进来的指令长度)
ir:指令寄存器,保存的是正在执行的指令
task_struct 里有程序计数器(pc 指针)
(2)CPU内部寄存器的数据是进程执行时的瞬时状态信息数据,该数据就是进程的上下文数据(随时随地都在高频变化)
(3)CPU内有很多寄存器(一套寄存器),但寄存器 ≠ 寄存器里面的数据(当前进程的上下文数据)

进程切换的核心就是上下文数据的保存和恢复
不做保护无法完成多进程的调度和切换

所有进程都要做如下工作
切走:将相关寄存器的内容保存起来
切回:将历史保存的寄存器数据恢复到寄存器中

意味着每次切换保存完上下文时,CPU都是全新的

3. 细节理解

  • 每个进程都要有自己的上下文数据,但CPU内部只有一套寄存器,该套寄存器被多个进程共享使用
  • 上下文数据保存在哪里?在内存里,进程的上下文寄存器数据,只要保存到当前进程的PCB中就可以了,早期Linux是存在tss_struct

五、调度

真实进程优先级[60, 99]
Linux真实的调度算法在这里插入图片描述
数组queue[140]:存放 struct task_struct* 指针,共140个元素
前100个下标对应的第0~99个元素给实时进程使用的
如果一个进程的优先级pri = 61,应该怎么放?
挂队列在下标为pri - startpri(60) + 100的位置(即 61 - 60 +100 = 101)
hash桶:相同优先级的进程会被挂接到一起

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值