在操作系统的世界里,进程是核心的“角色”。它如同现代社会中的“社交达人”,负责沟通与协作,为系统的高效运转提供动力。本文将以拟人化的视角,从进程的基本概念到其实现与操作,带你逐步了解这个神奇的世界。
1. 进程简介
进程是程序的一次运行活动,不仅包括代码本身,还涉及程序运行时所需的资源(如内存、文件句柄、CPU时间等)。总结来说,进程 = 内核数据结构对象 + 自己的代码和数据。
运行一个程序,操作系统会使用一个struct结构体对该程序进行描述,结构体中记录有该进程的一系列信息,该结构体就是该进程的内核数据结构对象,程序在执行时,内存会从磁盘上将代码和数据拷贝到内存中,这部分就是自己的代码和数据。
(1)进程的基本概念
(2)进程的基本操作
1. 创建进程:通过系统调用(如`fork`)生成新的进程。
2. 查看进程:使用工具(如`ps`、`top`)获取系统中所有活跃进程的信息。
3. 终止进程:通过信号或系统调用(如`kill`)结束一个进程。
2. 进程的“身份证”:PCB与task_struct
2.1 描述进程的PCB(Process Control Block)
进程控制块(PCB)是操作系统中用于描述进程的关键数据结构。它如同进程的“身份证”,记录了进程的核心信息,包括:
- 进程标识符(PID):唯一编号,便于区分不同进程。
- 进程状态:运行、等待、就绪等。
- CPU寄存器内容:记录进程切换时的上下文信息。
- 内存地址:进程代码段、数据段、堆栈段的位置。
PCB就是上面介绍的内核数据结构对象:
我们通过 PCB 结构体来描述进程的代码和数据,该结构体中包含指针以将各个进程连接起来。因此,进程的管理就变成了对进程链表的增、删、查、改操作。
比喻:
PCB是进程的档案袋,里面装满了进程的身份信息和工作记录。
2.2 Linux中的task_struct
在Linux中描述进程的结构体叫做task_struct,用来管理进程的核心数据结构。它包含了与进程相关的所有信息:
- 进程ID
- 进程状态
- 优先级
- 内存信息
- 打开的文件描述符表
进程的所有属性都可以直接或间接通过tast_struct找到。
通过内核源码,可以查看task_struct定义:
struct task_struct {
pid_t pid; // 进程ID
long state; // 进程状态
struct mm_struct *mm; // 内存信息
struct files_struct *files; // 打开的文件信息
...
};
比喻:
如果PCB是进程的档案袋,`task_struct`就是袋子里的详细内容。
在linux中task_struct就是内核数据结构对象:
3. 通过系统调用获取进程标识符
我们执行的所有指令、工具、自己的程序,运行起来全部都是进程。
3.1 getpid()
getpid是 Linux 中的一个系统调用,用于获取当前进程的进程 ID (PID)
3.2 getppid()
getppid用于获取当前进程父进程ID (PPID)
运行程序
通过上面的结果,我们发现子进程的id一直在变化,而父进程的id不变。
我们通过 ps ajx | head -1 && ps ajx | grep 17787 | grep -v grep 查看父进程
发现父进程是一个bash(命令行解释器),所以说bash也是一个进程。
4. 如何查看进程?操作系统的“监控器”
查看进程是操作系统管理的重要环节。在Linux中,可以使用多种工具查看和监控进程:
- `ps`命令:列出系统中所有运行的进程。
- `top`命令:动态显示进程的实时运行状态。
- `htop`工具:功能强大的交互式进程管理工具。
4.1 ps命令
(1)ps axj命令用于显示当前所有正在运行的进程的详细信息。下面是其中一部分的截图
(2)ps axj | grep process查看指定进程
当myprocess程序正在运行时:
grep也是一个进程,因此显示两个进程。
显示进程信息第一行
;
和 && 都用于在同一行中分隔多个命令。
不同点在于使用'
;
',shell 会按照从左到右的顺序依次执行这些命令,无论前一个命令执行成功(返回状态码为 0)还是失败(返回非零状态码),后续命令都会继续执行。
而 && 存在逻辑判断机制。shell 会按顺序执行命令,只有当前一个命令成功执行(返回状态码为 0)时,才会继续执行后续命令。
(3)隐藏grep进程
4.2 ls命令
ls /proc同样可以用来查看进程,proc目录里记录当前系统里所有进程的信息,每一个数字目录代表特定进程的pid,内容包含该进程在运行时的的动态属性,一旦退出该目录会被系统自动移除
当下面程序运行时:
我们可以通过pid查到该进程
杀死该进程后则无法查到
4.3 杀掉进程
(1)ctrl + c
(2)kill -9 pid
4.4 查看进程的属性
ls /proc/pid
(1)cwd是当前目录,这说明进程会记录下自己的当前路径。
我们在文件中使用fopen,在运行程序可以发现,当前目录下新建了一个文件hello.txt
使用chdir改变文件所在目录
文件被创建在了指定路径下
查看进程属性可以发现,进程所在目录为我们所指定目录
(2)exe是进程所对应的可执行文件
我们将这个文件删除 ,如果程序正在运行中,则程序不会终止,因为我们删除的是磁盘上对应的文件,而进程启动时,该进程的拷贝已经到内存了、
此时我们再来查当前进程的属性:
5. 通过系统调用创建进程-fork初识
`fork`是Linux中创建新进程的核心系统调用。它通过复制当前进程,生成一个新的子进程。
5.1 fork用来创建一个子进程
开始只有一个进程,通过fork创建后会有两个进程,并且这两个进程都会执行fork之后的代码
为什么两个进程都会执行之后的代码呢,通常是因为子进程会复制父进程的进程控制块(PCB),并且它们的大多数值是相同的。因此,父进程和子进程都会访问到相同的数据和代码段,导致它们都可以执行之后的代码。此时子进程没有自己的数据和代码,因为目前没有程序新加载。
5.2 fork的返回值
如果调用成果,子进程的ID作为返回值返回给父进程(这是一个整数),0 返回给子进程。调用失败-1被返回在父进程中。
5.3 父子执行不同代码
- 为什么fork给父子进程各自返回不同的返回值呢?
这是因为父 : 子 = 1 :n,意思是一个父进程可以有多个子进程,所以要给父进程返回子进程的id来区分不同的子进程,方便对子进程进行管理。
- 为什么一个变量,既等于0,又大于0,同时满足if和else
首先进程是独立的,一个进程的消失不影响其它进程,如果父子一方修改了数据,OS(操作系统)会把修改的数据子啊底层拷贝一份,让目标进程修改这个拷贝,这就是写时拷贝。
下面的结果可以看到,子进程的gval和父进程的gval是不一样的
比喻:
`fork`就像细胞分裂,原来的细胞(父进程)会分裂出一个新的细胞(子进程)。