Ciallo~(∠・ω< )⌒☆ ~ 今天,我将和大家一起学习 linux 进程~
❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️
澄岚主页:椎名澄嵐-CSDN博客
Linux专栏:https://blog.csdn.net/2302_80328146/category_12815302.html?spm=1001.2014.3001.5482
❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️
目录
壹 基本概念与基本操作
1.1 task_ struct
1.2 查看进程
1.3 创建进程
贰 进程状态
2.1 进程状态的概念
2.2 运行 阻塞 挂起
2.3 进程状态查看
2.4 僵尸进程
2.5 孤儿进程
叁 进程优先级
3.1 基本概念
3.2 查看系统进程
3.3 PRI and NI
3.4 查看进程优先级的命令
3.5 竞争、独立、并行、并发
肆 进程切换
伍 Linux2.6内核进程O(1)调度队列
5.1 优先级
5.2 活动队列
5.3 过期队列
5.4 active指针和expired指针
~完~
壹 基本概念与基本操作
进程是程序的一个执行实例,正在执行的程序等,或者说担当分配系统资源(CPU时间,内存)的实体。
进程信息(PCB)被放在一个叫做进程控制块的数据结构中,Linux操作系统下的PCB是: task_struct。进程的所有属性都可以直接或间接的通过task_struct找到。
进程 = PCB(struct_task)+ 自己的代码和数据 ~
对进程的管理就变成了对链表的增删查改 ~
1.1 task_ struct
task_ struct 中包含:
• 标示符: 描述本进程的唯一标示符,用来区别其他进程。
• 状态: 任务状态,退出代码,退出信号等。
• 优先级: 相对于其他进程的优先级。
• 程序计数器: 程序中即将被执行的下一条指令的地址。
• 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
• 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
• I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
• 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
• 其他信息
1.2 查看进程
历史上我们执行的所有指令,工具,自己的程序,运行起来都是进程~
我们可以通过系统调用getpid获取进程标示符~
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{while(1){sleep(1);printf("我是一个进程~,我的pid: %d\n", getpid());}
}
每次执行的进程值都不同~
终止进程可以使用 ctrl + C 或 kill -9~
进程的信息可以通过/proc 系统文件夹查看 ~
进程结束后此进程的pid为名的文件夹会被销毁 ~
因为cwd默认当前目录,所以fopen创建文件时会在当前目录下生成~
更改目录可以使用 chdir (也是系统调用)~
1 #include <stdio.h>2 #include <unistd.h>3 #include <sys/types.h>4 int main()5 {6 chdir("/home/zmzz");7 fopen("Ciallo.txt","a");8 while(1)9 {10 sleep(1);11 printf("我是一个进程~,我的pid: %d,我的父进程id: %d\n", getpid(), getppid());12 }13 }
getppid() 可以查看父进程,父进程不会变(bash命令行解释器本质是一个进程~)
每有一个用户就会有一个bash
1.3 创建进程
fork可以用来创建子进程~
1 #include <stdio.h>2 #include <unistd.h>3 #include <sys/types.h>4 int main()5 {6 printf("父进程开始运行:pid= %d\n", getpid());7 fork();8 printf("进程开始运行:pid= %d\n", getpid()); 9 }
fork有两个返回值~
一个父进程可以有多个子进程,是1:n的关系,父进程也需要知道子进程的pid进行控制,所以fork会给父子不同的返回值~
在fork函数中return id;语句执行前子进程已经被创建和调度了~所以fork会返回两次~
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{printf("父进程开始运行:pid= %d\n", getpid());pid_t id = fork();if (id < 0){perror("fork fail~");return 1;}else if (id == 0){// childwhile(1){sleep(1);printf("我是一个子进程~,我的pid: %d,我的父进程id: %d\n", getpid(), getppid());}}else{// fatherwhile(1){sleep(1);printf("我是一个父进程~,我的pid: %d,我的父进程id: %d\n", getpid(), getppid());}}printf("进程开始运行:pid= %d\n", getpid());
}
进程具有独立性,父子任何一方进行修改数据,OS会把修改的数据在底层拷贝一份,让目标进程修改这个拷贝~这个拷贝叫做采用写时拷贝~
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int gval = 100;int main()
{printf("父进程开始运行:pid= %d\n", getpid());pid_t id = fork();if (id < 0){perror("fork fail~");return 1;}else if (id == 0){printf("我是一个子进程~,我的pid: %d,我的父进程id: %d, gval: %d\n", getpid(), getppid(), gval);sleep(5);// childwhile(1){sleep(1);printf("子进程修改变量:%d->%d\n", gval, gval+10);gval += 10; // 修改printf("我是一个子进程~,我的pid: %d,我的父进程id: %d\n", getpid(), getppid());}}else{// fatherwhile(1){sleep(1);printf("我是一个父进程~,我的pid: %d,我的父进程id: %d, gval: %d\n", getpid(), getppid(), gval);}}printf("进程开始运行:pid= %d\n", getpid());
}
贰 进程状态
2.1 进程状态的概念
进程状态本质上就是task_struct内的一个整数。不同的整数代表不同的状态。
2.2 运行 阻塞 挂起
运行:进程在调度队列中,进程的状态都是running。
阻塞:等待某种设备或资源就绪。(键盘,显示器,网卡,磁盘,摄像头,话筒等)
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 信号让进程继续运行。
- t停止状态(tracing stopped): 被debug,断点,进程被暂停了。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
2.3 进程状态查看
ps aux / ps axj 命令
- a:显示一个终端所有的进程,包括其他用户的进程。
- x:显示没有控制终端的进程,例如后台运行的守护进程。
- j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息
- u:以用户为中心的格式显示进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等
2.4 僵尸进程
僵死状态(Zombies)是一个比较特殊的状态。
创建子进程的目的是为了帮父进程完成某种事情的,父进程需要知道子进程结果相关的信息。此信息会被存在task_struct中。如果父进程一直不回收子进程的退出信息,那么Z状态的PCB会一直存在,会引起内存泄漏问题。
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{pid_t id = fork();if(id == 0){//childint count = 5;while(count){printf("我是子进程,我正在运行:%d\n", count);sleep(1);count--;}}else{//parentwhile(1){printf("我是父进程,正在运行...\n");sleep(1);}}return 0;
}// while :; do ps ajx | head -1; ps ajx | grep myprocess; sleep 1; done
2.5 孤儿进程
父进程先退出,子进程被1号systemd(操作系统)进程领养,这个子进程就称之为“孤儿进程”。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{pid_t id = fork();if(id == 0){//childwhile(1){printf("我是一个子进程, pid = %d, ppid = %d\n", getpid(), getppid());sleep(1);}}else{// fatherint cnt = 5;while(cnt){printf("我是一个父进程, pid = %d, ppid = %d\n", getpid(), getppid());cnt--;sleep(1);}}return 0;
}// while :; do ps ajx | head -1 && ps ajx | grep myprocess;sleep 1;done
1号进程:
变成孤儿进程后,此进程会成为后台进程,ctrlC不能终止,需使用:
kill -9 此进程pid
叁 进程优先级
3.1 基本概念
进程优先级就是进程得到CPU资源的先后顺序~
CPU资源稀缺,所以导致要通过优先级确认谁先谁后的问题~
优先级是能得到资源,谁先谁后的问题。而权限是能不能得到资源的问题~
优先级也是一种数字, int, 值越低,优先级越高,反正,优先级越低~ 基于时间片的分时操作系统,考虑公平性,所以优先级可能变化,但变化幅度不会太大~
3.2 查看系统进程
ps -al | head -1 && ps -al | grep myprocess
• UID : 代表执行者的身份
• PID : 代表这个进程的代号
• PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
• PRI :代表这个进程可被执行的优先级,其值越小越早被执行
• NI :代表这个进程的NICE值
top r 10命令后:
3.3 PRI and NI
PRI即进程的优先级,或者说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高~
nice值表示进程可被执行的优先级的修正数值
所以,调整进程优先级,在Linux下,就是调整进程nice值,PRI=80+NICE值
Linux优先级范围为[60, 99],nice其取值范围是-20至19,一共40个级别。
3.4 查看进程优先级的命令
用top命令更改已存在进程的nice:
- top
- 进入top后按“r”–>输入进程PID–>输入nice值
3.5 竞争、独立、并行、并发
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
肆 进程切换
比如一个大学生要去当兵,去之前他得向辅导员申请保留学籍,一年之后回来时他又得向辅导员申请恢复学籍,这就相当于一次切换,学校就是CPU,导员是调度器,大学生就是进程,学籍是进程运行的临时数据(CPU内寄存器中的内容,当前进程的上下文数据),保留学籍恢复学籍分别相当于保存和恢复进程上下文数据,当兵就是从CPU上剥离下来了。
当前进程要将自己的进程硬件上下文保存到进程的task_struct中(TSS任务状态段)。
伍 Linux2.6内核进程O(1)调度队列
下图是Linux2.6内核中进程队列的数据结构~
一个CPU拥有一个runqueue, 如果有多个CPU就要考虑进程个数的负载均衡问题~
5.1 优先级
- 普通优先级:100~139(x - 60 + (140 - 40))
- 实时优先级:0~99(不关心)
5.2 活动队列
- 时间片还没有结束的所有进程都按照优先级放在该队列
- nr_active: 总共有多少个运行状态的进程
- queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!相当于一个哈希表管理的一个个链式队列
- 从该结构中,选择一个最合适的进程,过程是怎么的呢?
1. 从0下表开始遍历queue[140]
2. 找到第一个非空队列,该队列必定为优先级最高的队列
3. 拿到选中队列的第一个进程,开始运行,调度完成!
4. 遍历queue[140]时间复杂度是常数!但还是太低效了!
- bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率!
5.3 过期队列
过期队列和活动队列结构一模一样,过期队列上放置的进程,都是时间片耗尽的进程,当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算。
5.4 active指针和expired指针
- active指针永远指向活动队列
- expired指针永远指向过期队列
活动队列上的进程会越来越少,过期队列上的进程会越来越多,在合适的时候,只要交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!