什么是进程?
当我们写好一份代码时,编译生成可执行程序,将这个可执行程序运行起来,在运行期间,这就是一个进程。在Linux系统下,任何一个可以运行的软件,包括操作系统自己,都可以称之为进程。
操作系统如何管理进程
一台计算机中有数不清的进程,操作系统的作用就是要将这些进程有序的管理起来。既然要管理,那就离不开先描述,后组织,在现实生活中,公司管理员工,学校管理学生,都是先将要管理的对象用一个统一的方式进行描述,再将这些描述组织起来进行管理。
描述进程
PCB
- **PCB(process control block)翻译为进程控制块,它是操作系统描述进程的统称,而在Linux操作系统下的PCB是task_struct **。
- 在Linux系统下,每一个进程都有自己的task_struct,每个进程的task_struct中记录了与该进程有关的所有信息,就像进程的身份证一样。
task_struct的内容
- 标识符:用来描述本进程的唯一标识符,用来与其他进程区别。
- 状态:描述本进程目前的进程状态。
- 优先级:相对于其他进程的优先级。
- 程序计数器: 程序中即将被执⾏行的下⼀一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
- 上下⽂文数据: 进程执⾏行时处理器的寄存器中的数据[休学例⼦子,要加图CPU,寄存器。
- I/O状态信息: 包括显⽰示的I/O请求,分配给进程的I/O设备和被进程使⽤用的⽂文件列表。
- 记账信息:可能包括处理器时间总和,使⽤用的时钟数总和,时间限制,记账号等。
- 其他信息
组织进程
进程的运行方式(以单CPU计算机为例)
在使用计算机的时候,通常可以同时打开很多个软件,那就是说计算机可以同时运行多个进程,但CPU只有一个,操作是如何做到多个进程同时运行的呢?
当我们要打开一个进程,操作系统就会将该进程的进程状态改变为运行态(R),在操作系统内核中,操作系统会根据进程的优先级,将处于运行态(R)的进程的PCB采用队列的数据结构链接起来,在任意时刻,计算机的CPU只能运行一个进程,当该进程在CPU上运行了足够长的时间之后,调度器就会将该进程从CPU上卸载,并且将该进程的PCB重新插入运行态PCB队列的队尾重新排队,这样就能保证每一个进程相同的时间段内,都能得到被CPU运行的机会。而通常进程在CPU上运行的时间片都是很短暂的,所以就会给造成CPU在同时处理多个进程的假象。
进程标识符(PID)
系统中的进程的PID会被记录在/proc/目录下。
获取一个进程的PID
-
pgrep [进程名]
-
使用top命令查看
-
通过系统调用
-
#include<sys/types.h> #include<unistd.h> pid_t getpid(void);//获取进程pid pid_t getppid(void);//获取父进程pid
-
进程状态
/*
* 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(running):处于该状态的进程并不一定正在被CPU运行,也有可能正在排队等待CPU运行它。
- 睡眠状态S(sleeping):意味着进程在等待某事件的完成(这里的睡眠状态有时也叫做可中断睡眠状态)。
- 可以使用kill命令终止进程。
- 磁盘休眠状态D(Disk sleep):在这个状态的进程通常在等待I/O的结束(有时也叫做不可中断睡眠状态)。
- 深度睡眠,不能使用kill命令终止进程。
- 停止状态T(stopped):可以通过发送 SIGSTOP 信号给进程来停⽌止(T)进程。这个被暂停的进 程可以通过发送 SIGCONT 信号让进程继续运⾏行。
- 死亡状态X(dead):该状态为进程返回状态。
僵尸状态(Z状态)
- 一个进程已经终止,但是没有被父进程回收,这样的进程被称为僵尸进程。
- 当一个进程因为某种原因终止时,操作系统并不会立即将它从系统中清除。操作系统会让该进程处于一种已终止的状态中,直到该进程被它的父进程回收,当父进程回收已终止的子进程时,操作系统会将子进程的退出码交给父进程,然后清理掉子进程。
在Unix系统中,除了0号进程(即PID=0的交换进程)以外的所有进程都是由其他进程使用系统调用接口fork创建的,这里调用fork创建新进程的进程即为父进程,而被创建出来的进程则为子进程,所以除了0号进程以外的所有进程,都一定有一个子进程,0号进程是系统引导时创建的一个特殊进程,在其调用fork创建出子进程(1号进程pid=1,又称init)后,0号进程就转变为交换进程(有时也被称为空闲进程),而1号进程(pid=1,又称init进程)就是系统中所有其他进程的祖先。
僵尸进程的危害
- 僵尸进程会造成内存泄露。
- 父进程如果不回收子进程,父进程就不会知道子进程的退出码。
孤儿进程
- 当父进程创建出子进程之后,子进程没有退出,这时候父进程终止了,这样的进程就叫做孤儿进程。
- 当一个孤儿进程出现时,操作系统会让1号进程称为该孤儿进程的父进程。
进程优先级
查看当前系统中程序的优先级
- 使用top/ps -l等命令,查看PRI于NI行
PRI&NI
- PRI:表示进程的优先级。
- 取值范围为[19, 60]
- NI:进程的nice值
什么是进程的nice值
nice值是Linux系统用来调整进程被执行的优先级的变量,当一个进程的PRI值越小,该进程就会被越优先执行,而一个进程的PRI值的运算要遵循一个公式PRI(new) = PRI(old) + NI(nice值)。
- nice值的取值范围为[-20, 19]。
调整进程的nice值
- 开始执行前就调整nice值:nice -n [数字] [运行进程]
- 调整已经运行的进程的nice值:renice [数值] -p [进程pid]
优先级为什么要有上限与下限?
- 为了保证调度器调度进程的均衡性。
创建进程
- 系统初始化
- 系统调用
- 用户创建
系统调用接口fork
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);//创建子进程
- 返回值:失败返回-1;给子进程返回0;给父进程返回子进程的pid。
- 使用fork()创建出来的子进程与父进程共享fork以下的代码,数据相互独立(采用写时拷贝)。
进程地址空间
- 32位程序的虚拟地址空间为4G(因为在32位程序中,地址的大小是4字节,一个地址指向一个字节,所以它最多可以指向2^32个字节,即就是4GB)。此处以32位程序为例:
在进程的PCB中,包含一个指针,这个指针指向一个结构体mm_struct。
mm_struct
操作系统中的每一个进程都有自己的mm_struct,使每一个进程都有一个独立的地址空间。mm_struct定义在include/linux/mm_types.h中,其中的域抽象了进程的地址空间, mm_struct为每一个进程都描述了一个像下图中分区明确的虚拟内存空间。
页表&MMU
页表是维护进程虚拟地址空间与实际物理内存之间映射关系的一张表,页表&MMU可以将相同虚拟地址空间的同一个地址映射进不同的实际物理内存中。MMU是计算机的一个硬件。
使用简单的代码来描述该问题:
int num = 0;
int main()
{
num = 1;
return 0;
}