一:什么是进程
课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。
进程=内核数据结构(PCB)+ 程序代码和数据
操作系统是如何管理进程的呢? 先描述,再组织。
先描述:1. 处于磁盘的程序加载到内存 2.操作系统在内核中生成一个结构对象(PCB),linux下称为task_struct. task_struct指向内存中对应的代码和数据,并存有其相关的属性 信息。
再组织:把每个task_struct通过链表这种数据结构进行管理。就可以管理每个进程。
task_struct 内容
1.标示符: 描述本进程的唯一标示符,用来区别其他进程。
2.状态: 任务状态,退出代码,退出信号等。
3.优先级: 相对于其他进程的优先级。
4.程序计数器: 程序中即将被执行的下一条指令的地址。
5.内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
6.上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
7.I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
8.记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
二:如何查看进程
1.ps指令
当一个进程启动后,我们可以用ps axj 查看进程并用grep 查找目标进程
[root@hcss-ecs-178e ~]# ps axj|grep myexe
3112 3170 3170 3081 pts/0 3170 S+ 1001 0:00 ./myexe
3171 3191 3190 3171 pts/1 3190 S+ 0 0:00 grep --color=auto myexe
head -1打印第一行内容
可以用; 或者 && 在一行中输出多个指令
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3112 3575 3575 3081 pts/0 3575 S+ 1001 0:00 ./myexe
3171 3579 3578 3171 pts/1 3578 S+ 0 0:00 grep --color=auto myexe
grep --color=auto myexe 这个进程是grep指令,因为带有myexe关键字所以被一起查找到。
可以用grep -v grep 反向过滤。
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe|grep -v grep
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3112 3587 3587 3081 pts/0 3587 S+ 1001 0:00 ./myexe
2./proc
根目录下有proc目录,它是一个虚拟文件系统,包含了系统和进程的信息。它提供了关于内核、系统状态和正在运行的进程的数据,用户可以通过读取这些文件来获取实时信息。
proc目录里面蓝色的是什么?
这些也是目录,目录名数字代表进程的PID,里面存有进程的属性。
(每个进程的PID都是唯一的,一个进程重新启动PID可能会和上一次不同)
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3651 3780 3780 3621 pts/0 3780 S+ 1001 0:00 ./myexe
3668 3784 3783 3668 pts/1 3783 S+ 0 0:00 grep --color=auto myexe
[root@hcss-ecs-178e ~]# cd /proc/3780
[root@hcss-ecs-178e 3780]# ll
total 0
dr-xr-xr-x 2 wws wws 0 Sep 20 16:44 attr
-rw-r--r-- 1 wws wws 0 Sep 20 16:44 autogroup
-r-------- 1 wws wws 0 Sep 20 16:44 auxv
-r--r--r-- 1 wws wws 0 Sep 20 16:44 cgroup
--w------- 1 wws wws 0 Sep 20 16:44 clear_refs
-r--r--r-- 1 wws wws 0 Sep 20 16:44 cmdline
-rw-r--r-- 1 wws wws 0 Sep 20 16:44 comm
-rw-r--r-- 1 wws wws 0 Sep 20 16:44 coredump_filter
-r--r--r-- 1 wws wws 0 Sep 20 16:44 cpuset
lrwxrwxrwx 1 wws wws 0 Sep 20 16:44 cwd -> /home/wws
-r-------- 1 wws wws 0 Sep 20 16:44 environ
lrwxrwxrwx 1 wws wws 0 Sep 20 16:44 exe -> /home/wws/myexe
dr-x------ 2 wws wws 0 Sep 20 16:44 fd
dr-x------ 2 wws wws 0 Sep 20 16:44 fdinfo
-rw-r--r-- 1 wws wws 0 Sep 20 16:44 gid_map
.....
exe -> /home/wws/myexe 这个路径是myexe程序所处的路径
cwd -> /home/wws 指的是当前目录,即myexe程序所处的目录
当myexe生成文件时就会放在当前当前目录下。
如果想改变当前工作路径可以在程序中使用chdir函数
#include<unistd.h>
chdir("更改为的路径“)
成功返回0
注意如果改为根目录,生成的文件如果用户没有根目录的写权限,也是不能生成文件的。
3.top指令
是一个常用的 Linux 命令行工具,用于实时监控系统的进程和资源使用情况。它显示了系统中运行的进程信息,包括 CPU 和内存使用率。
M
:按内存使用量排序。P
:按 CPU 使用率排序。k
:杀死一个进程(需要输入 PID)。q
:退出top
三:getpid getppid 获取PID
#include <sys/types.h>
这个头文件时,通常是为了定义一些数据类型和结构,特别是在进行系统调用或处理文件和进程时。常见的类型包括:
pid_t
:用于表示进程 ID。size_t
:用于表示对象的大小。off_t
:用于表示文件的偏移量。gcc -o myexe code.c -g [wws@hcss-ecs-178e ~]$ ./myexe 我的PID:4857,我的父进程PID:4289 我的PID:4857,我的父进程PID:4289 我的PID:4857,我的父进程PID:4289 我的PID:4857,我的父进程PID:4289 ^C [wws@hcss-ecs-178e ~]$ ./myexe 我的PID:4858,我的父进程PID:4289 我的PID:4858,我的父进程PID:4289 我的PID:4858,我的父进程PID:4289 我的PID:4858,我的父进程PID:4289
子进程每次重新启动pid都会变化,但父进程不会变,因为进程可以有多个子进程但自能有一个父进程。
可以看到myexe的父进程是bash,bash是什么?
bash是命令行解释器,也是一种进程。bash为什么要创建子进程?
Bash 创建子进程来执行程序主要是为了实现进程隔离和资源管理。通过在子进程中运行命令,Bash 可以:
- 避免影响主进程:子进程的错误或终止不会直接影响到 Bash 本身。
- 简化信号处理:主进程可以更好地管理和处理来自子进程的信号。
- 实现并行执行:可以同时运行多个子进程,而主进程可以继续接收用户输入。
四:fork()创建子进程
1.fork()用法
fork()函数会创建一个子进程,成功给父进程返回子进程的PID,给子进程返回0.失败给父进程返回-1.
[wws@hcss-ecs-178e ~]$ ./myexe
我是父进程,我的PID5443,我的子进程PID5444
我是子进程,我的PID:5444,我的父进程PID5443
我是父进程,我的PID5443,我的子进程PID5444
我是子进程,我的PID:5444,我的父进程PID5443
我是父进程,我的PID5443,我的子进程PID5444
我是子进程,我的PID:5444,我的父进程PID5443
我是子进程,我的PID:5444,我的父进程PID5443
我是父进程,我的PID5443,我的子进程PID5444
我是父进程,我的PID5443,我的子进程PID5444
我是子进程,我的PID:5444,我的父进程PID5443
我是子进程,我的PID:5444,我的父进程PID5443
我是父进程,我的PID5443,我的子进程PID5444
2.fork()怎么创建进程
我们知道进程=内核数据结构+程序代码和数据
fork的子进程和父进程不同,它在磁盘中没有对应的可执行程序,不能加载到内存中。所以子进程的程序代码和数据会继承父进程的,task_struct也是根据父进程为模板再进行部分调整。再连入进程表中,就建立了一个子进程。
父子进程代码共享一份,但数据独享。相互独立,互不影响。
eg.代码中定义的全局变量,父进程改变了全局变量,但子进程的全局变量数据不会改变。
问题:为什么fork有两个返回值?给父子进程分别返回值
fork()函数在return 时,已经建立了子进程,父进程return一次,子进程return一次就会有两个返回值。通常情况下,
fork()
调用后,父进程和子进程会几乎同时开始执行,谁先执行取决于调度器的决定。
五:进程状态
进程状态可以简单分为以下几种
在分时操作系统中,CPU在处理进程时是轮流进行处理,多个进程组成运行队列,cpu处理一段时间后,再按队列顺序继续处理下一个进程。只是cpu处理速度快,我们在使用过程中没发现。
1.运行
我们知道每个进程都有与之对应的task_struct,这些结构体通过链表的方式组成队列,连入到struct runqueue。
我们把它称作运行队列,处于运行队列的task_struct的进程状态为运行。
2.阻塞
当一个程序中出现scanf需要从外设中读数据,导致cpu不能继续处理时,该进程的task_struct就不能继续在运行队列了。就要去访问外部设备了
操作系统是如何管理外部设备的呢?
和对进程管理一样,先描述在组织。每个外设都有对应的结构体struct device,结构体间再通过链表连接起来,进行管理。
每个struct device中都有task_struct*wait queue 等待队列,每当有进程需要获取外部数据,task_struct就要进入等待队列。
进入等待队列的task_struct的进程状态为阻塞。
3.挂起
一般当操作系统内存不足时,为了节省空间,会把处于阻塞状态的代码和数据换出到磁盘当中,等到在阻塞队列出队列的时候,再把代码和数据换入到内存。(磁盘中有专门进行进行换入换出的空间)
这种就叫阻塞挂起,当然也有运行挂起,这种状态下不能被cpu处理。
挂起就是通过时间换取空间的做法。
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运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
(interruptibl
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的
进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可
以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
1.R状态
什么是R状态,Linux下的R状态和上面运行状态概念一样,处在运行队列里就是R状态。
运行上面程序
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
5861 6565 6565 5832 pts/2 6565 S+ 1001 0:00 ./myexe
6378 6569 6568 6378 pts/3 6568 S+ 0 0:00 grep --color=auto myexe
这个程序不是死循环吗?为什么不是处于R,而是S(阻塞)?
因为printf函数是向外设打印信息,而与外设进行交互是非常慢的,所以大部分时间都是位于阻塞状态,等到外部资源就绪,进程才能被cpu再度调度,变为R.
(处于sleep时也是阻塞状态)
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
5861 7085 7085 5832 pts/2 7085 R+ 1001 0:02 ./myexe
6378 7089 7088 6378 pts/3 7088 R+ 0 0:00 grep --color=auto myexe
2.S状态
S状态可以理解为阻塞状态,可以被杀死。可终断睡眠,浅睡眠。
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
5861 7283 7283 5832 pts/2 7283 S+ 1001 0:00 ./myexe
6378 7287 7286 6378 pts/3 7286 S+ 0 0:00 grep --color=auto myexe
3.D状态
和S状态不同的是,处于D状态的程序不能被杀死。
为什么要有D状态呢?
在内存不足时,操作系统会杀掉睡眠的进程,来腾出空间。但如果该进程正在向磁盘写入数据,被杀死就会数据泄露。这时候就需要D状态来保证不会被系统杀掉。
4.T状态
T状态暂停状态,和阻塞状态不同的是,T状态是主动暂停,需要用户自主决定是否恢复,而阻塞状态是正在等待某些事件(如I/O操作完成、信号或资源的可用性),是被动暂停,等条件满足自动恢复。
kill
kill -9 杀死进程
kill -19 暂停进程
kill -18 继续进程
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
21487 22120 22120 21458 pts/0 22120 S+ 1001 0:00 ./myexe
21844 22124 22123 21844 pts/1 22123 S+ 0 0:00 grep --color=auto myexe
[root@hcss-ecs-178e ~]# kill -19 22120
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
21487 22120 22120 21458 pts/0 21487 T 1001 0:00 ./myexe
21844 22128 22127 21844 pts/1 22127 S+ 0 0:00 grep --color=auto myexe
可以看到kill -19 暂停进程后,状态从S+变为T。
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
21487 22120 22120 21458 pts/0 21487 T 1001 0:00 ./myexe
21844 22128 22127 21844 pts/1 22127 S+ 0 0:00 grep --color=auto myexe
[root@hcss-ecs-178e ~]# kill -18 22120
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
21487 22120 22120 21458 pts/0 21487 S 1001 0:00 ./myexe
21844 22132 22131 21844 pts/1 22131 S+ 0 0:00 grep --color=auto myexe
可以看到kill -18 恢复进程后,状态从T变为S。
S和S+有什么不同吗?
S+指进程在前台运行,可以用Ctrl+c结束进程。
S指进程在后台运行,此时不能响应Ctrl+c,只能用kill -9 杀死进程。
5.t状态
我们在调试代码时,运行到断点停止,此时的状态就是追踪暂停t状态。
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
21487 22328 22328 21458 pts/0 22328 S+ 1001 0:00 gdb ./myexe
22328 22334 22334 21458 pts/0 22328 t 1001 0:00 /home/wws/./myexe
21844 22341 22340 21844 pts/1 22340 S+ 0 0:00 grep --color=auto myexe
6.Z状态
我们在代码结束时 一般会写return 0 这其实是退出码,根据退出信息来判读程序是否正常结束。
在linux下通过 echo $? 可以查看最近一次程序的退出码
[wws@hcss-ecs-178e ~]$ ls code.c dir1 install.sh makefile myexe [wws@hcss-ecs-178e ~]$ echo $? 0
而Z状态(僵尸状态)存在的意义就是为父进程/系统提供退出信息。
当一个子进程结束时,它的代码和数据会从内存中消失,但它的task_struct从进程队列中退出,但不会从内存中消失。此时这个进程就会处于Z状态,需要由父进程回收。如果不回收,就会造成内存泄漏。
如果父进程是bash的话,子进程结束后,会执行回收机制,看不到僵尸状态。
我们可以用fork(),生成子进程。
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 27309 28139 28139 27280 pts/0 28139 S+ 1001 0:00 ./myexe 28139 28140 28139 27280 pts/0 28139 S+ 1001 0:00 ./myexe 27326 28144 28143 27326 pts/1 28143 S+ 0 0:00 grep --color=auto myexe [root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 27309 28139 28139 27280 pts/0 28139 S+ 1001 0:00 ./myexe 28139 28140 28139 27280 pts/0 28139 Z+ 1001 0:00 [myexe] <defunct> 27326 28149 28148 27326 pts/1 28148 S+ 0 0:00 grep --color=auto myexe
可以看到子进程从S+变成Z+,保留退出信息。此时需要父进程主动回收,不然会出现内存泄漏。
7.X状态
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
六:孤儿进程
父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程。
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
30295 30440 30440 30266 pts/0 30440 S+ 1001 0:00 ./myexe
30440 30441 30440 30266 pts/0 30440 S+ 1001 0:00 ./myexe
30442 30464 30463 30442 pts/1 30463 S+ 0 0:00 grep --color=auto myexe
关闭父进程
[root@hcss-ecs-178e ~]# kill 30440
[root@hcss-ecs-178e ~]# ps axj|head -1;ps axj|grep myexe
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 30441 30440 30266 pts/0 30295 S 1001 0:00 ./myexe
30442 30476 30475 30442 pts/1 30475 S+ 0 0:00 grep --color=auto myexe
可以看到子进程的父进程改变了,被进程1领养了。
用top指令查询进程
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 840 root 20 0 1295272 19320 5304 S 0.3 1.9 38:33.83 hostguard 1 root 20 0 125520 3308 1964 S 0.0 0.3 0:09.87 systemd
PID 为 1 的进程通常是systemd它是系统和服务管理器,负责启动和管理系统中的其他进程和服务。
此时子进程会处于后台运行,不能通过ctrl+c停止。