第三部分:2---进程理解/Linux下进程初识

目录

操作系统如何管理进程?

进程的结构体:

操作系统如何加载进程?

CPU如何调度进程?

进程如何在多个队列排队:

offsetof宏:

进程标识符:

程序打印自己的pid和ppid:

杀死进程:

———————————————

getpid()和getppid()如何得到当前进程的pid/ppid:

父进程和子进程的关系:

动态进程目录:

当前目录是什么?

———————————————

fork创建进程:

创建子进程的目的?

创建子进程后一般写法思路:

fork之后,干了什么事情?

fork之后父子进程谁先运行?

fork之后,为什么会return两个返回值?

代码共享,数据独立:

———————————————

进程状态通俗理解:

运行状态 / 运行队列:

阻塞状态 / 资源的等待队列:

阻塞挂起状态:

swap分区:

———————————————

Linux下,进程的运行状态:

运行状态:

前台进程 / 后台进程:

休眠状态:

暂停状态 / 18和19信号:

僵尸状态:

父进程不存在僵尸状态:

孤儿进程和进程领养:

———————————————

排队的本质——确认优先级:

进程优先级的本质:

通过NI值修正进程优先级值PRI:

限定优先级在一定范围的原因:

进程的并行和并发:

进程切换和上下文保存:


操作系统如何管理进程?

进程是由操作系统创建的->操作系统是由C语言写的->进程需要被C语言写的操作系统管理->C语言通过结构体管理数据->进程相当于一个个结构体->结构体中的成员是进程的一个个属性。

进程的结构体:

进程的结构体被称为“进程的PCB”,当进程被创建PCB就会被创建。Linux下PCB叫做task_struct。

操作系统如何加载进程?

  • 操作系统先将可执行程序从磁盘加载到内存中。

  • 操作系统为加载的进程创建PCB,用于管理进程信息。

  • 将新创建的PCB链接到PCB链表上(Process Table)。

CPU如何调度进程?

  • CPU通过调度算法(轮转调度、优先级调度等),找到下一个需要运行的进程==找到进程的PCB。

  • 操作系统保存当前运行中的进程的上下文(寄存器、程序计数器等状态信息) 。

  • 操作系统加载新进程的上下文,将其寄存器、程序计数器等信息加载到 CPU 中。

  • CPU开始执行新进程的指令。

进程如何在多个队列排队:

  • 进程排队排的是进程PCB。

  • PCB之间的链接属于双向链表。

  • 一般情况下,双向链表存储两个指针,一个指向前前一个节点,一个指向后一个节点。

  • PCB中特定存储一个结构体dlist存放这两个指针。

  • 当进程被排到某个队列中,就创建一个这种结构体,存放该PCB在当前队列的前后指针。

  • 这样PCB每被排序到一个队列,就创建一个dlist,就能实现PCB多队列排队。

offsetof宏:

  • offsetof 宏的定义通常依赖于 __builtin_offsetof 或者通过指针运算来实现。

  • 其原理是,通过计算某个结构体成员的地址减去结构体首地址,得到成员相对于结构体开头的偏移量。

进程标识符:

  • 操作系统中进程标识符pid(process id)会唯一标识一个进程。

  • pid在进程创建时被分配,是唯一的。当进程被释放,pid会被回收给其他新进程使用。

ps ajx | grep my_program
a: 显示所有终端的进程,包括其他用户的进程。
j: 以作业格式显示,包含会话、进程组等信息。
x: 显示没有控制终端的进程,例如守护进程。
grep 命令过滤指定名称的进程

  • 每条命令都可以当作是一个进程,当调用grep过滤指定名称的进程时。他也会作为一个进程运行。

ps ajx | grep my_program | grep -v grep
//加上grep -v grep来过滤grep自身的进程。
//grep过滤器,在过滤某个关键字时,如果关键字不包含特殊符号可以不加双引号,如果包含特殊符号就需要加上双引号确保能够正确解析字符串。比如mycode可以不加双引号,my code就需要加上双引号。

程序打印自己的pid和ppid:

#include <stdio.h>
#include <sys/types.h>//调用getpid要包含这两个库
#include <unistd.h>

int main()
{
	pid_t pid=getpid();//调用失败返回0
	pid_t ppid=getppid();
	printf("%d",pid);
}

杀死进程:

kill -9 pid 
//发送9信号,杀死pid进程

———————————————

getpid()和getppid()如何得到当前进程的pid/ppid:

  • pid和ppid存放在进程的PCB中,当要得到他时需要访问PCB。

  • 操作系统不希望用户直接访问PCB。

  • 于是提供了“系统调用接口”,getpid和getppid得到PCB中的pid和ppid后,返回给用户。

父进程和子进程的关系:

  • 子进程是由父进程创建的,创建时:子进程会以父进程为模板,继承父进程的部分数据和状态。

动态进程目录:

  • 在Linux的根目录下有一个proc目录,其中动态维护当前存货的进程信息。

ls /proc 

  • 其中的子目录是当前存在的进程,子目录以进程pid命名。

ls /proc/pid -l//查看子目录中的文件 

  • 其中每个进程的目录中,有一条:exe->path,他会指向进程在磁盘中可执行程序的路径。

  • 有一条:cwd->path,指向当前目录。

当前目录是什么?

  • 当进程被创建,PCB会记录可执行程序当前的路径,存放在cwd中。

  • 之后对当前进程的路径操作,如果不写为绝对路径,默认使用cwd路径作为相对路径。

chdir("PATH");
//在可执行程序中更改当前目录,更改后进程的PCB中的cwd也会同时修改为PATH。 

  • 每个进程都有当前目录==工作目录。

———————————————

fork创建进程:

pid_t fork(void)//创建进程 

  • 使用fork创建一个子进程,fork之后的代码会被父进程和子进程同时执行。

  • 进程A创建了一个子进程B,子进程的父进程是进程A。

  • fork失败返回-1,创建成功给父进程返回子进程的pid,给子进程返回0。

创建子进程的目的?

  • 让子进程协助父进程完成单进程无法完成的工作。

  • 让子进程和父进程分别完成一项工作。

  • 基于fork的返回值对于父子进程有区别,通过分支让父子进程执行不同的代码段。

创建子进程后一般写法思路:

pid_t id=fork();
if(id<0) return 1;//说明fork失败
else if(id==0) {}//对子进程返回0,这里写子进程的执行逻辑
else {} //写父进程的执行逻辑

 

fork之后,干了什么事情?

  • fork创建子进程之后,系统中会多一个PCB,就是子进程的PCB。

  • 由于fork创建的子进程是直接在系统创建,所以这个进程没有自己的可执行程序。

  • 所以子进程需要共享父进程加载到内存的可执行程序==共享代码和数据。

fork之后父子进程谁先运行?

  • 进程创建后,在调度序列中被排序。

  • 由调度器算法和PCB中的调度信息共同决定当前调度的进程。

fork之后,为什么会return两个返回值?

  • fork先找到父进程PCB,根据父进程PCB初始化子进程的PCB。

  • 让子进程的PCB指向父进程的可执行程序。

  • 将子进程PCB放到调度队列。

  • fork之后的代码是父子进程之间共享的,包括return。

  • 当return时,父进程调度完成return一次,子进程调度完成return一次。返回两次也就是两个值。

代码共享,数据独立:

  • 父子进程,共用同一份代码。

  • 数据相互独立,子进程在用到某个成员时,从父进程的数据写时拷贝一份到自己的数据中。

———————————————

进程状态通俗理解:

  • 进程状态就是进程PCB中的一个变量的值,这个值来自于一个枚举类型。

  • 当变量的值对应枚举类型不同的成员时,代表进程处在对应的状态。

  • 状态的变化,就是修改变量的值为对应枚举类型的成员。

运行状态 / 运行队列:

  • 每个CPU都会维护一个运行队列。

  • 进程创建PCB,并且被排序到运行队列中,他所处在的状态就是运行状态。

  • 具体实现就是:进程中控制进程状态的变量被赋值为特定值,进程被链接到运行队列,PCB添加一个dlist标记队列前后节点。

阻塞状态 / 资源的等待队列:

  • 当一个进程需要访问某个资源时,如果资源尚未准备好,进程无法继续执行。

  • 资源的状态结构体中会自己维护一个队列,这个队列用于存储那些:因资源当前不可用而被阻塞的进程。

  • 当前正在运行的进程如果发现需要的资源不可用,那么它会从运行队列中被移出,并放入所需资源的等待队列。并且它的状态将被修改为“阻塞”。

  • 操作系统会调度其他进程运行,直到阻塞的进程所需的资源变得可用。资源的状态结构体会通知在等待队列中的进程,并将其从阻塞状态恢复到就绪状态。这些进程会被重新放回运行队队列。

阻塞挂起状态:

  • 当操作系统检测到内存严重不足时,它可能会将那些处于等待状态且短时间内不会被执行的进程 (PCB+可执行程序)从内存中转移到磁盘,从而节省出一部分内存。这一过程称为“挂起”。

  • 处于资源等待队列的进程,由于处于阻塞状态,短时间也不会被执行,不如直接挪动到磁盘来节省出空间。

  • 将进程从内存移至磁盘的操作会带来性能上的开销,因为磁盘I/O操作比内存访问要慢得多。但在内存极度紧张的情况下,这种代价是必要的,以防止操作系统因为内存耗尽而崩溃。

  • swap分区,不建议设置很大,可能导致频繁的内存和磁盘的交互,导致操作系统变慢。一般设置到和内存一样大或者两倍即可。

swap分区:

  • swap分区存储的就是被挂起的进程。

  • swap的读写速度远不及物理内存。如果系统频繁使用swap,整体性能会明显下降,可能导致系统响应变慢,甚至出现“卡死”的现象。

  • 如果swap分区过大,可能会诱导系统频繁将数据交换到磁盘上,导致更多的I/O操作,反而降低了系统的整体性能。

  • 一般建议swap分区的大小设置为物理内存的1倍或更小。  

———————————————

Linux下,进程的运行状态:

运行状态:

R (Running or runnable):进程正在运行或在运行队列中等待运行。

前台进程 / 后台进程:

  • 当stat显示的进程状态后面有+,比如R+,S+。代表这个进程为前台进程。前台进程通常是由用户直接控制的,比如通过终端启动的程序。比如有一个可执行程序mycode,./mycod直接运行就是以前台进程方式运行的。

  • 如果stat显示的进程状态没有+,代表这个进程为后台进程。以后台进程的方式运行某个进程,需要在进程后面加取地址符号(&)。例如:./mycode &  

休眠状态:

S (Sleeping):进程处于休眠状态,即它正在等待某个事件(如I/O操作完成)。这类进程可以被唤醒。
D (disk sleep):不可中断的休眠状态。进程通常在等待硬件设备时处于此状态,不会处理任何信号,不可被中断,直到进程调用完成。处于D状态的进程过多,系统资源被大量消耗,可能导致系统性能下降或者崩溃。 

暂停状态 / 18和19信号:

T (Stopped):进程已经停止运行。可能是因为收到了SIGSTOP信号或者被调试器暂停。
kill -18 pid  //暂停进程 -SIGCONT
kill -19 pid  //继续进程 -SIGSTOP

僵尸状态:

Z (Zombie):僵尸进程,进程已终止但尚未被其父进程回收。此状态下的进程只剩下一个PCB,系统资源已经被释放。

  • 当进程进入僵尸状态时,可执行程序和大部分资源已经被释放,但PCB仍然保留。

  • 当程序终止时会return一个值,会由操作系统写入到进程的PCB中(exit_code)。

  • 之后父进程在读取子进程的退出状态的同时适释放PCB == 将进程状态从 ‘Z’改为 ‘X’。

  • 再判断是否需要做出下一步动作,例如重新启动子进程、记录日志或采取其他相应的措施。

  • 僵尸进程本身并不消耗大量资源,但如果大量僵尸进程积累,可能会导致系统进程表耗尽,影响系统的进程管理。

父进程不存在僵尸状态:

  • 僵尸状态是专门针对子进程的,每个父进程也有他的父进程。

  • 当一个子进程的父进程终止,父进程的父进程就会处理他的退出状态并释放PCB。

孤儿进程和进程领养:

  • 当子进程的父进程先被终止,那么子进程就会变为孤儿进程。

  • 孤儿进程终止变为僵尸进程后,没有父进程读取其退出状态,并为其释放PCB,会导致内存泄漏。

  • 孤儿进程会被init进程(Linux系统中通常是systemd)收养并处理。当这些被收养的子进程终止时,init进程会读取它们的退出状态,防止它们成为僵尸进程。

———————————————

排队的本质——确认优先级:

  • 进程排队的本质源于资源的有限性与进程需求的无限性之间的矛盾。

  • 操作系统通过调度和排队机制管理这个矛盾,以确保资源被有效利用,同时为所有进程提供合理的执行机会。

进程优先级的本质:

  • 进程优先级的本质,是一个存在于PCB中int变量。

  • 变量的值越小,优先级越大。

  • linux中,进程优先级的范围为:60~99,默认进程的初始优先级为80。

ps -l //可以查看进程优先级,PRI字段就是优先级数值 

通过NI值修正进程优先级值PRI:

  • linux下,不允许直接修改进程优先级,需要通过nice值=>NI值,来修正优先级数值。

  • PRI(new) = PRI(old) + NI

 top//启动任务管理器 -> r//重设nice值 -> 输入pid -> 新的nice值。

  • 普通用户可以调低进程的优先级,但是不能调高,使用超级用户可以调高进程优先级

  • 由于PRI范围为60~99,基于pri初始值为80,所以调整nice值的范围为-20~19。

限定优先级在一定范围的原因:

  • 某些进程优先级过低,可能长时间无法被调度。导致进程饥饿。

  • 通过将进程优先级限制在一定的范围内,可以确保即使是最低优先级的进程,也能在合理的时间内获得CPU资源。

  • 还可以帮助操作系统在资源分配中实现公平性和有效性,避免极端优先级设置导致某进程一直被调用。

进程的并行和并发:

  • 并行:多进程分别在多CPU下,同时运行。

  • 并发:多进程在单CPU下,高速切换运行。

进程切换和上下文保存:

  • 一个CPU通常只有一套寄存器硬件。所以这套寄存器对不同进程都是共享。

  • 当CPU切换进程时,会将寄存器中的内容保存到进程的PCB中,也可以认为是保存在内存中(通常是堆栈)。

  • 当切换要调度的新进程后,不会直接清空存储在寄存器中上一个进程的数据,而是直接将新进程的数据覆盖到寄存器上。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

S+叮当猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值