【Linux】Linux 进程基础
参考博客:https://blog.csdn.net/sjsjnsjnn/article/details/125533127
进程
1.基本概念
- 课本概念:程序的一个执行实例,正在执行的程序等
- 内核观点:担当分配系统资源(CPU时间,内存)的实体。
2.描述进程——PCB
实际上,我们所写的代码在运行起来就是进程,如何管理进程呢?
即先描述,再组织;
- 任何进程在形成之时,操作系统要为改进程创建PCB——进程控制模块;简单的将,PCB就是一个结构体( Linux操作系统下的PCB是: struct task_struct ),里面存放了进程相关的属性信息;
3.进程和程序的区别
首先,我们编写好的程序,在经过编译处理之后,所产生的文件(可执行程序)会放在中,当我们运行程序时,操作系统将磁盘上的文件加载到内存中,同时为它创建PCB(进程控制模块),这两个组合起来才是进程。
3.1 task_struct-PCB的一种
在Linux中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
3.2 task_struct的内容分类
1.标示符: 描述本进程的唯一标示符,用来区别其他进程。
2.状态: 任务状态,退出代码,退出信号等。
3.优先级: 相对于其他进程的优先级。
4.程序计数器: 程序中即将被执行的下一条指令的地址。
5.内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
6.上下文数据: 进程执行时处理器的寄存器中的数据。
7.I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
9.其他信息
四、如何查看进程
4.1 通过系统文件/proc,来查看当前所有的进程
ls /proc/
4.2 通过ps指定查看进程
-
ps指令用于报告当前系统的进程状态。可以搭配kill指令随时中断、删除不必要的程序。ps命令是最基本同时也是非常强大的进程查看命令,使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等,总之大部分信息都是可以通过执行该命令得到的。
-
如果不指定参数,ps默认输出与终端相关的进程,比如下图对应的
bash
ps
ps axj
中的参数分别代表:
a
:显示所有用户的进程(包括其他用户的进程)。x
:显示没有控制终端的进程(即不与终端关联的进程,如后台服务)。j
:使用任务格式(Job Control Format),显示额外的进程关系信息。
ps axj
各列含义:
PPID
:父进程 ID(Parent Process ID)。PID
:进程 ID(Process ID)。PGID
:进程组 ID(Process Group ID),同一组的进程拥有相同的 PGID。SID
:会话 ID(Session ID),通常是登录 shell 或守护进程的 PID。TTY
:控制终端(?
表示无终端关联)。TPGID
:前台进程组 ID(-1 表示无前台进程)。STAT
:进程状态(如S
表示睡眠,R
表示运行)。UID
:进程所有者的用户 ID。TIME
:进程占用 CPU 的累计时间。COMMAND
:启动进程的完整命令行。
五、获取pid和ppid
我们想要获取到pid和ppid,就要用到系统调用接口:
pid_t getpid( void )
— 返回的是子进程IDpid_t getppid( void )
— 返回的是父进程ID
我们可以通过 man指令对这些函数进行详细说明的查看
man getpid
man getppid
- 下面的代码,打印出来当前执行进程的
pid
和ppid
void test1()
{while (true){std::cout << "child pid = " << getpid() << std::endl;std::cout << "father pid = " << getppid() << std::endl;sleep(1);}
}
- 通过下面的
shell
指令,可以查看我们对应的process-test
程序相关的进程
ps axj | head -1 && ps axj | grep process-test
- 可以发现,打印出来的子进程
ID
和父进程ID
与查询出来的是一致的
六、进程的创建
6.1 四种主要事件会导致进程的创建
1.系统初始化
2.正在运行的程序执行了创建程序的系统调用
3.用户请求创建一个新进程
4.一个批处理作业的初始化
6.2 用户如何请求创建一个新进程
通过fork函数来进行进程的创建,我们可以man fork查看相关的函数信息。这是一个系统调用,它会创建一个与调用进程相同的副本。在调用fork之后,这两个进程(父进程和子进程)拥有相同的内存映射。
- 可以通过
fork
函数创建一个子进程 - 下面的代码中,我们创建了子进程,然后在一个循环中打印进程的
id
void test2()
{fork(); // 创建子进程while (true){static int sum = 0;std::cout << "sum = " << sum++ << "child pid = " << getpid() << " father pid = " << getppid() << std::endl;sleep(2);}
}
-
可以发现,对于同一个
sum
,子进程和父进程都打印了一遍,相当于这两个进程执行了同一段代码
-
这里我们通过
kill
命令,终止了子进程,此时打印出来的sum
只有一段了,只有一个进程在运行
kill -9 6336
从上述的结果可以看出,main函数的进程和fork创建的进程打印的结果是一样的,并且通过pid和ppid发现,fork的父进程就是main函数的进程,说明fork所创建出来的子进程和父进程在内存上映射。
6.3 如何让父子进程各有所需
以上创建的子进程所做的事和父进程是一样的,显然意义并不大,我们要能够让所创建的子进程做和父进程不一样的事,才是我们想要的。那如何实现呢?
首先,对于fork是有两个返回值的
- 如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
- 如果子进程创建失败,则在父进程中返回 -1。
void test3()
{int ret = fork();while (1){if (ret == 0){std::cout << "I am child, process ID = " << getpid() << std::endl;std::cout << "father process ID = " << getppid() << std::endl;std::cout << std::endl;sleep(1);}else if (ret > 0){std::cout << "I am father, process ID = " << getpid() << std::endl;std::cout << "father process ID = " << getppid() << std::endl;std::cout << std::endl;sleep(1);}else{std::cout << "error :" << strerror(ret) << std::endl;}}
}
- 运行结果如下,可以发现,子进程和父进程执行了不同的任务
- 与子线程类似,子进程会执行
fork
函数后面的程序
七、进程的状态
7.1 进程的状态有哪些
CPU对进程处理,取决于进程当前进程所处的状态,CPU对于不同状态的进程会采取不同的措施。
下面的是关于进程状态的描述代码
/*
* 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 */ //追踪状态,类似于vs下打断点后直接运行到断点处
"X (dead)", /* 16 */ //死掉的进程
"Z (zombie)", /* 32 */ //僵尸进程
};
7.2 进程状态的查看
- 通过下面的
shell
指令,可以查看系统进程的状态
ps aux
a
:显示所有用户的进程(包括其他用户的进程,不仅仅是当前用户)。u
:使用详细格式(User-oriented format)显示进程信息,包含用户、CPU、内存等资源占用情况。x
:显示没有控制终端的进程(即不与终端关联的进程,如后台服务、守护进程)。
结果如下图所示
7.3 进程状态的分析
7.3.1 运行状态 — R
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
-
上图想表达的意思是,进程A处于运行中,在一段时间后,就会切换到进程B…,这个时间很快,CPU运行这些进程是采用了时间轮转调度算法。
-
在时间片轮转调度算法中,系统根据先来先服务的原则,将所有的就绪进程排成一个就绪队列,并且每隔一段时间产生一次中断,激活系统中的进程调度程序,完成一次处理机调度,把处理机分配给就绪队列队首进程,让其执行指令。
-
当时间片结束或进程执行结束,系统再次将cpu分配给队首进程。
7.3.2 浅度睡眠状态 — S
-
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 **(interruptible sleep))。
-
下面的代码展示了进程的两种状态,运行状态和浅度睡眠状态
void test4()
{int ret = fork();if (ret == 0){while(true){std::cout << "I am child, process ID = " << getpid() << std::endl;sleep(1);}}else if (ret > 0){while(1){sleep(1);}}else{std::cout << "error :" << strerror(ret) << std::endl;}
}
- 通过下面的
shell
命令可以查看进程的状态,这里过滤为2709
相关pid
的进程
ps axj | ps aux | grep 2709
- 可以发现,存在
R
和S
的两种状态,分别对应运行和浅度睡眠状态 - 处于S状态的进程,是可以被立即终止的,可以通过
kill -9 2709
指令终止进程
7.3.3 深度睡眠状态 — D
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- 例如,当进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)
7.3.4 停止状态 — T
-
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
-
下面的程序演示了进程浅度休眠状态,我们发送
SIGSTOP
信号让它停止
void test5(){pid_t ret = fork();if(ret == 0){while(1){std::cout << "I am child, process ID = " << getpid() << std::endl;std::cout << "father process ID = " << getppid() << std::endl;std::cout << std::endl;sleep(1);}}else if(ret > 0){std::cout << "I am father, process ID = " << getpid() << std::endl;std::cout << "father process ID = " << getppid() << std::endl;std::cout << std::endl;sleep(60);exit(1);}else{std::cout << "error :" << strerror(ret) << std::endl;}
}
- 关于停止的信号,我们可以通过下面的
shell
指令查询
kill -l
- 查询对应的
pid
,然后发送停止信号
kill -SIGSTOP 3515
- 查询停止后的进程状态,可以发现
3515
进程已经处于停止状态了
ps axj | grep 3515
- 可以发送
SIGCONT
信号,让处于停止状态的进程继续运行
kill -SIGCONT
7.3.5 僵死状态 — Z
-
僵死状态(Zombies)是一个比较特殊的状态。当子进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程,僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
-
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态;
7.3.6 死亡状态 — X
- X死亡状态(dead):死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态。
八、僵尸进程与孤儿进程
8.1 僵尸进程是什么
- 有如下代码,我们执行之后,子进程会不断的打印数据,父进程等待子进程的过程中,我们立刻杀掉子进程,那么子进程就会处于僵尸状态,而此时程序还在执行,父进程在等待子进程退出的状态,我们把这种进程称之为僵尸进程
void test5(){pid_t ret = fork();if(ret == 0){while(1){std::cout << "I am child, process ID = " << getpid() << std::endl;std::cout << "father process ID = " << getppid() << std::endl;std::cout << std::endl;sleep(1);}}else if(ret > 0){std::cout << "I am father, process ID = " << getpid() << std::endl;std::cout << "father process ID = " << getppid() << std::endl;std::cout << std::endl;sleep(60);exit(1);}else{std::cout << "error :" << strerror(ret) << std::endl;}
}
- 先查看
kill
前,进程的状态
ps axj | head -1 && ps axj | grep 2737
- 调用
kill
指令,终止子进程,此时父进程就会一直等待子进程
kill -9 2737
- 查看对应的子进程,此时子进程
2737
已经变成了僵尸进程
ps axj | head -1 && ps axj | grep process-test
8.2 僵尸进程的危害
-
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态
-
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护
-
一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费
-
会导致内存泄漏
8.3 孤儿进程
-
刚刚提到了僵尸进程是由于子进程先退出而父进程没有对子进程的退出信息进行读取;
-
那么父进程先退出,子进程在进入僵尸状态后,其父进程未能对其做出处理,那么就称该进程是孤儿进程。
-
若是一直不处理孤儿进程的退出信息,那么孤儿进程就会一直占用资源,此时就会造成内存泄漏。
-
因此,当出现孤儿进程的时候,孤儿进程会被1号init进程领养,此后当孤儿进程进入僵尸状态时就由int进程进行处理回收。
- 通过
kill
父进程的方式,让子进程变成了孤儿进程,此时的ppid
发生了变化 - 这里具体是
1550
,查阅发现,这里优先被用户级会话的进程收养
- 查看
1550
进程,是当前用户下的守护进程
九、优先级
9.1 基础概念
- cpu资源分配的先后顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能
9.2 查看进程优先级
- 下面的
shell
指令可以查看系统进程的详细信息,包括优先级
ps -l
- 我们很容易注意到其中的几个重要信息,有下:
- UID : 代表执行者的身份
- PID : 代表这个进程的代号
- PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
- PRI :代表这个进程可被执行的优先级,其值越小越早被执行
- NI :代表这个进程的nice值
9.3 PRI & NI
-
PRI: 即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序, 此值越小 进程的优先级别越高;
-
NI: 就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值 ;
-
PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
-
当nice值为负值的时候 ,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行;
-
调整进程优先级,在Linux下,就是调整进程nice值; nice其取值范围是-20至19,一共40个级别;
注:Linux下,PRI(old)默认是80
9.4 更改进程的优先级
9.4.1 top 命令
- 用top命令更改已存在进程的nice
top
- 进入后,按下r键,然后输入对应的
pid
,回车
- 然后就可以修改对应的PRI值了
- 重新查看对应的值,发生了改变,具体为什么变成了
99
,我也不太明白
9.4.2 renice命令
- 用renice命令更改已存在进程的nice
- 这个指令需要管理员身份,使用
sudo
,这里将pid = 1574
的进程的NI
值改为0
sudo renice 0 1574
- 再次查看对应的进程信息,发现NI值已经发生改变了
ps -al
9.5 其他概念
- 竞争性 : 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性 : 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行 : 多个进程在多个CPU下分别,同时进行运行,这称之为并行
- 并发 : 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
更多资料:https://github.com/0voice