linux 进程

简介: linux 进程

进程是可执行程序的实例,是资源分配和调度的基本单位。

进程:动态执行的程序 + 虚拟内存 + 虚拟 CPU

1、进程的状态

进程的基本状态

  • 运行态Running:进程正在占用 CPU。
  • 就绪态Ready:进程具备执行条件,等待获取 CPU。
  • 阻塞态Blocked:进程等待某一事件而暂停运行。

一个进程从运行态到阻塞态是主动行为,而从阻塞态到就绪态是被动行为,需要其他进程的协助

此外,进程的状态还包括

  • 创建态 New:进程正在被创建
  • 结束态 Exit:进程正在从系统中消失

为减少阻塞态的进程占用物理内存,在虚拟内存管理的操作系统中,通常会把阻塞态的进程换出到硬盘,等待再次运行时,再从硬盘换入到物理内存。这种进程没有占用物理内存的情况称为挂起态。ctrl + z

挂起态分为

  • 阻塞挂起态:进程在外存并等待某个事件的出现
  • 就绪挂起态:进程在外存,但只要进入内存,就能立刻执行

image.png

查看进程的状态

# 查看进程状态
 ps -elf 'UNIX风格
 ps aux  'BSD风格
 # 查看系统的内存占用
 free
 # 动态显示系统当中的进程
 top

2、进程的组织

进程映像(进程实体):PCB + 程序段 + 数据段

  • 进程控制块 PCB:描述进程的基本情况和运行状态,进而控制和管理进程。
  • 程序段:进程调度到 CPU 执行的代码段,可被多进程共享。
  • 数据段。

PCB 是进程存在的唯一标识。创建进程就是创建进程映像中的 PCB,撤销进程就是撤销进程的 PCB。其中存储了各种静态信息,包括进程描述信息、进程控制和管理信息、资源分配清单、CPU 相关信息等。

例如:进程描述信息

  • 进程标识符 PID:唯一标识进程。
  • 用户标识符 UID:进程归属的用户,用于共享和保护。
// 返回调用进程的 PID
 pid_t getpid(void);
 // 返回父进程的 PID(创建调用进程的进程)
 pid_t getppid(void);
 // 获取用户ID
 uid_t getuid(void);
 // 获取有效用户ID 
 uid_t geteuid(void);
 // 获取组ID
 gid_t getgid(void);
 // 获取有效组ID
 gid_t getegid(void);

为了方便进程的调度和管理,内核将各进程的 PCB 组织起来,常用的方法有链接方式和索引方式。

链接方式:内核把相同状态的 PCB 链接到一个双向循环链表,组成任务队列。链表的节点类型为 task_struct ,也就是进程控制块 PCB。不同状态对应不同的任务队列,例如:就绪队列和阻塞队列

索引方式:将相同状态的进程组织在一个索引表中,索引表的表项指向相应的 PCB,不同状态对应不同的索引表等。

3、进程的控制

3.1、进程的创建

创建进程的过程(创建原语)

  • 为新进程分配 PID,并申请一个空白的 PCB
  • 为进程分配资源
  • 初始化 PCB
  • 将 PCB 插入到就绪队列,等待被调度运行;

fork 函数

父进程通过调用 fork 函数创建一个子进程。子进程是父进程的拷贝,子进程继承了父进程的地址空间(地址空间相同但是独立),共享文件。最大区别在于二者拥有不同的 PID。

fork 函数只被调用一次,却会返回两次:在父进程中,fork 返回子进程的 PID。在子进程中,fork 返回 0 。

// 返回值:子进程返回值为 0,父进程返回子进程的 PID。
 pid_t fork(void);

fork 拷贝和共享

  • 用户态拷贝:堆、栈、数据段、代码段,进程上下文,文件流,文件缓冲区
  • 内核态:部分拷贝(文件描述符数组),部分共享(文件描述符指向的文件对象)

fork 原理

  • 上半部:拷贝地址空间,修改子进程 task_struct,调整 PID 和 PPID 。随后,将子进程放入就绪队列等待调度,并将子进程的 fork 的返回值修改为 0,父进程的返回值设置为子进程的 PID。该过程不可抢占。
  • 下半部:将返回值返回给两个进程,随后修改 PC 指针,让各进程继续执行后续的命令

fork 的写时复制

写时复制(Copy on Write, COW):fork 调用时页表复制,并且将两个进程的每个页面标记为只读,父子进程共用一块物理内存。当有进程试图写操作时,触发缺页中断,从而进行物理内存的复制,并更新其页表项指向这个新的物理内存,然后恢复这个页面的可写权限。总之,谁修改,谁拷贝内存,子进程指向这块新内存。


写时复制的最充分地使用了稀有的物理内存,只有在发生写操作的时候,系统才会去复制物理内存,从而避免物理内存的复制过程导致进程长时间阻塞。

exec 函数族

在当前进程的上下文加载并执行一个程序

fork 函数在子进程中运行相同的程序,新的子进程是父进程的一个副本;exec 函数在当前进程的上下文加载并运行一个新的程序,会覆盖当前进程的地址空间。

exec 原理

  • 代码段,数据段被替换
  • 堆栈清空
  • PC指针重新返回代码段的开始

例:实现 system 函数功能

#include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
 extern char **environ;
 int Mysystem(const char* CMD) {
     if (CMD == NULL) {
         return 1;
     }
     if(fork() == 0) {
         execl("/bin/sh", "sh", "-c", CMD, NULL);
         _exit(127);
     }
     wait(NULL);
 }
 // 测试样例:./mysystem date 实现 sysytem("date")
 int main(int argc,char*argv[]) {
     Mysystem(argv[1]);
     return 0;
 }

3.2、进程的终止

终止进程的过程(撤销原语)

  • 根据被终止进程的 PID,检索 PCB,读取该进程的状态
  • 若进程处于运行态,立即终止该进程的执行,然后将 CPU 资源分配给其他进程;
  • 如果进程还有子进程,则将该进程的子进程交给 init 进程(pid = 1)回收
  • 将该进程所拥有的全部资源,或归还给其父进程,或归还给操作系统;
  • 将该 PCB 从所在队列中删除

exit 函数

以 status 退出状态来终止进程。

#include <unistd.h>
 void _exit(int status);

当一个进程由于某种原因终止时,内核并不是立即把它从系统中删除。相反,进程被保持在一种已终止的状态,直到被它的父进程回收。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程,至此该进程被终止。

* 僵死进程

僵死进程:一个终止但并未被回收的进程。

父进程需要及时回收已终止的子进程。因为僵死进程即使没有运行,仍然消耗系统的内存资源。

父进程调用 wait 或 waitpid 函数来等待它的子进程终止或停止。

#include <sys/types.h>
 #include <sys/wait.h>
 // 随机等待一个子进程
 pid_t wait(int *status);
 // 等待指定 pid 的子进程
 pid_t waitpid(pid_t pid, int *status, int options);

* 孤儿进程

孤儿进程:父进程先于子进程退出。自动被养父 init 进程(pid = 1)收养。当一个孤儿进程退出后,它的资源清理会交给它的父进程(init 进程)来处理。但在此之前,它一直消耗系统的资源,要尽量避免。

3.3、进程的阻塞和唤醒

阻塞进程的过程:进程等待事件,主动调用 Block 原语

  • 找到将要被阻塞进程 PID 对应的 PCB
  • 如果该进程为运行状态,则保护其现场,将其状态转为阻塞态,停止运行
  • 将该 PCB 插入到相应事件的等待队列,将 CPU 调度给其他就绪进程

唤醒进程:其他相关进程调用,Wakeup 原语

唤醒进程的过程如下:

  • 在该事件的等待队列中找到相应进程的 PCB
  • 将其从队列中移出,并置其状态为就绪态
  • 把该 PCB 插入就绪队列中,等待调度

Block 原语和 Wakeup 是一对作用相反的原语,必须成对使用。

3.4、进程的切换

进程切换:处理机从一个进程的运行转到另一个进程上运行,这个过程中,进程运行环境产生了实质性变化。

调度是指资源分配给哪一进程的行为,是一种决策;切换是指实际分配的行为,是执行行为。一般来说,先有资源的调度,然后才有进程的切换。

进程切换的过程

  • 保存 CPU 上下文,包括程序计数器和其他寄存器
  • 更新 PCB信息
  • 把进程的 PCB 移入相应的队列
  • 选择另一个进程执行,更新 PCB
  • 更新内存管理的数据结构
  • 恢复 CPU 上下文

4、进程间通信

进程间主要通信方式有:管道、共享内存、信号、信号量、消息队列、套接字

参考我的博客:linux 进程间通信

5、进程调度算法

进程调度算法

  • 先来先服务:选择最先进入队列的
  • 短作业优先:选择完成时间最短的
  • 优先级调度:选择优先级别最高的
  • 高响应比优先:选择响应比最高的
  • 时间片轮转:总是选择就绪队列中第一个进程,但仅能运行一个时间片
  • 多级反馈队列:时间片轮转 + 优先级调度

参考

相关文章
|
8月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
10月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
327 32
|
6月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
269 67
|
5月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
157 16
|
5月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
124 20
|
4月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
114 0
|
4月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
138 0
|
4月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
103 0
|
4月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
98 0
|
8月前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
338 34