linux进程


一、进程的概念

进程是一个正在执行的程序,是程序的一个执行实例,包含进程控制块、进程的代码和数据。
按内核的观点,进程担当分配系统资源(CPU时间,内存)的实体

二、进程控制块

1 进程控制块概述

进程控制块(Process Control Block, PCB)是操作系统内核用于管理和调度进程的一个重要数据结构,它包含了描述和控制该进程所需的所有信息。
每个进程在系统中都有一个对应的进程控制块,以下是进程控制块中通常包含的关键元素:

  • 进程标识符( PID):进程的唯一标识。
  • 进程状态:当前进程的状态,如就绪、运行、阻塞等。
  • 进程优先级:决定进程获得CPU时间片的顺序。
  • 程序计数器(PC):程序中下一条要执行的指令的地址。
  • 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
  • 内存管理信息:包括基址、界限寄存器值、页表或段表指针等,用于实现虚拟地址到物理地址的转换。
  • 上下文数据:进程执行时需要的其他CPU寄存器的值。
  • I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 处理器状态信息:当进程被中断时,CPU寄存器的内容(如程序计数器PC、栈指针SP等)会被保存在这里,以便进程后续恢复执行。
  • 资源清单:列出进程已分配或请求的系统资源,如内存、I/O设备等。
  • 地址空间信息:描述进程的地址空间布局,包括代码段、数据段、堆、栈的起始地址和大小。
  • 队列指针:指向进程所在的各种队列(如就绪队列、等待队列)的指针,便于操作系统进行调度。
  • 信号处理信息:定义了进程如何响应操作系统发出的信号。
  • 父进程ID和子进程信息:记录父子进程间的关联,用于实现进程间的通信和同步。

2 task_struct结构体

task_struct是Linux内核中负责描述进程的数据结构,它包含了进程的所有信息,如执行状态、打开的文件、地址空间等。这个结构体在内核源码中的include/linux/sched.h头文件中定义。
部分代码如下:

struct task_struct {
	/*
	 * offsets of these are hardcoded elsewhere - touch with care
	 */
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	unsigned long flags;	/* per process flags, defined below */
	int sigpending;
	mm_segment_t addr_limit;	/* thread address space:
					 	0-0xBFFFFFFF for user-thead
						0-0xFFFFFFFF for kernel-thread
					 */
	struct exec_domain *exec_domain;
	volatile long need_resched;
	unsigned long ptrace;

	int lock_depth;		/* Lock depth */

/*
 * offset 32 begins here on 32-bit platforms. We keep
 * all fields in a single cacheline that are needed for
 * the goodness() loop in schedule().
 */
	long counter;
	long nice;
	unsigned long policy;
	struct mm_struct *mm;
	int processor;
	...
}

这一节简要介绍了进程控制块。更详细的描述可以参考以下链接:
https://www.jianshu.com/p/691d02380312
https://blog.csdn.net/npy_lp/article/details/7292563
https://blog.csdn.net/npy_lp/article/details/7335187

三、操作系统如何管理进程

我们已经学过进程控制块,已经操作系统将进程描述成进程控制块数据结构,在Linux中就是task_struct(先描述)。
在用数据结构体描述进程后,再将这些数据结构组织起来,在Linux中用双向链表将task_struct结构体组织起来(再组织)。
通过这样的方式,操作系统将对进程的管理转换成对数据结构对象的管理。

操作系统对软件和硬件的管理都是通过这种先描述再组织的方式进行管理的,比如对底层硬件网卡、硬盘等的管理,也是先将网卡、硬盘等描述成数据结构体,再将这些数据结构组织起来进行管理。

参考链接:
https://blog.csdn.net/xbhinsterest11/article/details/129308859
https://blog.csdn.net/m0_64028711/article/details/128715902

四、进程的状态

1 进程的状态

进程在其生命周期中会经历多种状态,这些状态反映了进程与操作系统之间交互的不同阶段以及资源分配的情况。常见的进程状态包括:

  • 就绪(Ready)状态:当一个进程已经准备就绪,等待CPU分配时间片执行,但尚未获得CPU控制权时,该进程处于就绪状态。就绪状态的进程位于就绪队列中,操作系统根据一定的调度算法选择就绪队列中的一个进程投入运行。
  • 运行(Running)状态:当一个进程获得了CPU控制权并且正在执行时,它处于运行状态。在单核处理器系统中,任何时刻最多只有一个进程处于运行状态;而在多核或多处理器系统中,可以有多个进程同时处于运行状态。
  • 阻塞(Blocked)状态:当进程因等待某一事件的发生而暂停执行时,比如等待I/O操作完成、等待信号量、等待锁或者等待用户输入等,进程进入阻塞状态。在阻塞状态下,进程不占用CPU资源,被移出运行队列,加入到相应的等待队列中,直到等待的事件发生才会被唤醒并转换为就绪状态。阻塞状态也被称为等待状态。
  • 新建(New)状态:进程刚被创建但还未进入就绪状态之前,可以认为它处于新建状态。在此期间,操作系统会为新进程分配必要的资源,如PID、内存空间、初始化PCB等。
  • 终止(Terminated)状态:当进程完成任务或因某种原因(如错误、异常)终止执行时,它进入终止状态。此时,操作系统会回收其占用的资源,释放其内存空间,并从进程表中删除相应的PCB。终止状态也被称为退出状态。

有的操作系统还有挂起状态,在资源不足的情况下,操作系统对在内存中的程序进行合理的安排,其中有的进程被暂时调离出内存,这些进程将进入挂起状态。

2 linux的进程状态

上一节介绍了操作系统进程的典型状态,不同的操作系统实际进程的状态略有差异。Linux的内核源码对于进程的状态的分类如下:

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
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"状态:运行状态正在cpu上执行或者正在等待被cpu执行(处于典型状态中的运行状态或就绪状态)。
  • "S"状态:可中断睡眠状态,进程在等待某个事件(如I/O操作完成、信号接收)发生,此状态下的进程可以响应信号而被唤醒,然后转为就绪状态。
  • "D"状态:不可中断睡眠状态,进程同样在等待某个事件,但在这个状态下,进程不会响应信号,也不会被调度,直到等待的事件完成。这种状态通常发生在进行某些内核操作时,如磁盘I/O,保证重要操作的原子性。
  • "T"状态:停止状态,可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • "t"状态:调试停止状态,我们在调试程序的时候就是处于这种状态,比如使用gdb进入调试时就是这种状态。
  • "X"状态:死亡状态:这个状态只是一个返回状态,你不会在任务列表里看到这个状态。例如一个程序跑完了,会返回一个值。
  • "Z"状态:僵尸状态,这是一个特殊的状态,当子进程退出,但其父进程没有读取其返回状态时,该子进程就会处于僵尸状态。处于僵尸状态的进程不会被杀死,即使使用kill -9也无济于事,这就可能会造成内存泄露。
  • 孤儿进程:在子进程跑完之前,父进程已经跑完了或被杀死了,子进程就会变成孤儿进程,被操作系统接手,并且变为后台运行。

进程的状态信息储存在进程的PCB中,操作系统通过从PCB中查看进程的状态来分配进程。

详细介绍参考链接:
https://blog.csdn.net/fengjunziya/article/details/130482909

五、进程地址空间

1 进程地址空间概述

图片来自https://blog.csdn.net/attemptendeavor/article/details/122539046
图片来自https://blog.csdn.net/attemptendeavor/article/details/122539046。

上图是进程地址空间,这段空间自下而上是从低地址向高地址增长的。其中栈是向地址减小的方向增长(栈先使用高地址),堆是向地址增大的方向增长(堆先使用低地址),堆和栈之间的空间是共享区。

  • 程序代码存放在代码区
  • 字符串常量存放在字符常量区
  • 初始化的全局变量和局部静态变量存放在初始化数据区
  • 未初始化的全局变量和局部静态变量存放在未初始化数据区
  • 动态分配的空间存放在堆区。堆区的内存由进程分配和释放。
  • 进程间通信通过共享内存方式共享的数据存放在共享区
  • 局部变量存放在栈区。栈区的内存当进程走出局部变量作用域时由操作系统释放。
  • 命令行参数存放在命令行参数区。命令行参数指的是在命令行上敲入的一些参数,main函数的第二个参数就是负责获取命令行参数的,比如选项和命令等。
  • 环境变量存放在环境变量区。main函数的第三个函数就是负责接收环境变量的。

进程地址空间,会在进程的整个生命周期内一直存在,直到进程退出,这也就解释了全局变量为什么会一直存在,原因是未初始化数据,初始化数据,这些区域是一直存在的。

2 进程地址空间是虚拟地址

进程地址空间是一种虚拟地址,每个进程都认为自己拥有全部的内存空间。比如对于32位系统,每个进程都认为自己有4GB的空间,每个进程都可以向内存申请空间,只要能接受都会给你,不能接受操作系统会直接拒绝,但是并没有什么影响,进程依旧认为自己有4GB的空间。
Linux内核用struct mm_struct结构体来描述进程地址空间,结构体如下:

struct mm_struct
{
    unsigned long code_start;//代码区
    unsigned long code_end;
    
    unsigned long init_start;//初始化区
    unsigned long init_end;
    
    unsigned long uninit_start;//未初始化区
    unsigned long uninit_end;
    
    unsigned long heap_start;//堆区
    unsigned long heap_end;
    
    unsigned long stack_start;//栈区
    unsigned long stack_end;
    //...等等
}

进程地址空间与物理内存地址通过页表映射。

3 进程地址空间存在的意义

  1. 保护物理内存不受到任何进程内地址的直接访问,在虚拟地址到物理地址的转化过程中方便进行合法性校验
  2. 将内存管理和进程管理进行解耦
  3. 让每个进程,以同样的方式(虚拟地址),看待代码和数据,明确程序运行的地址

详细介绍参考:
https://blog.csdn.net/attemptendeavor/article/details/122539046


  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值