Linux进程

进程概念

获取进程pid方式(概念)

1.获取所有的pid信息

部分获取ps ajx | head -1 && ps axj | grep 文件名

ps ajx | head -1 && ps axj | grep 文件名 | grep -v grep可以过滤掉grep中携带进程部分信息的内容

因为我没有运行test.o所以未开辟进程,所以没有进程信息

ps ajx | grep 文件名

2.示例代码

每个进程都有属于自己的PID(身份码)

在Linux系统里,PID即"Process IDentifier",是进程标识符 ,以下是详细介绍:

基本概念

PID是系统为每个进程分配的独一无二的非负整数 。每个进程从创建起就被赋予特定PID,借此在系统中被唯一标识。就像现实中每辆车都有独特车牌号,方便交通管理,PID便于操作系统对进程进行管理和区分。

作用

  • 进程管理:**是进程的身份标识。**系统借助PID识别、跟踪、控制进程,如使用 kill 命令通过指定PID向进程发送信号,实现进程终止(如 kill -9 <PID> 可强制终止进程 ); waitpid 函数能让父进程等待特定子进程结束 。

  • 资源分配与调度:操作系统依据PID管理进程资源分配,决定CPU时间、内存等资源如何分配给各进程,保障系统高效稳定运行 。

  • 进程间通信:进程可利用PID识别通信对象,实现进程间通信和同步协作 。

回顾上文我们简单的了解了进程,首先介绍的是fork.

在Linux中, fork 是一个用于创建新进程的系统调用函数 ,它在Unix和类Unix操作系统中被广泛使用。以下是关于它的详细介绍:

函数原型与头文件

  • 原型: pid_t fork(void); , pid_t 实质是 int ,在 <sys/types.h> 中定义 。

  • 头文件:使用时需包含 <unistd.h> ,有时也会用到 <sys/types.h> 。

工作原理

调用 fork 时,系统会创建一个与调用进程(父进程)几乎完全相同的新进程(子进程)。具体过程如下:

  1. 资源分配:系统为新进程分配资源,如存储数据和代码的空间。

  2. 数据复制:将父进程的很多属性复制到子进程,包括数据空间、堆、栈等资源的副本,但父子进程并不共享这些存储空间 。不过,代码段是共享的,即父子进程从 fork 函数调用之后的下一条指令开始执行。

返回值特点

fork 调用一次却返回两次,在不同进程中有不同返回值:

  • 在父进程中:返回新创建子进程的进程ID(PID),是一个大于0的整数 。通过这个返回值,父进程能识别并后续操作子进程。

  • 在子进程中:返回值为0 。子进程可据此判断自身身份。

  • 出错时:返回 -1 ,可能原因有当前进程数达到系统上限( errno 设为 EAGAIN ) 或系统内存不足( errno 设为 ENOMEM )。

在上述代码中,执行 fork 后产生父子进程。**子进程中 fpid 为0,打印子进程相关信息;父进程中 fpid 是子进程PID,**打印父进程及子进程PID信息 。

应用场景

  • 多进程并发服务器:有客户端连接时,父进程用 fork 创建子进程处理请求,自身继续监听新连接,提升并发处理能力 。

  • 任务并行处理:将不同任务分发给子进程并行执行,加快任务完成速度,比如批量文件处理等场景 。

提出以下问题

  1. 为什么 fork 要给子进程返回0,给父进程返回子进程 pid
  • 子进程返回0:子进程通过返回0能方便地知道自己是子进程,因为0是一个特殊值,与任何有效的进程ID都不同。这样子进程可以在后续代码中根据这个返回值来执行特定于子进程的逻辑。

  • 父进程返回子进程 pid :父进程得到子进程的 pid 后,就可以通过这个 pid 来对特定的子进程进行管理和控制,比如等待子进程结束、向子进程发送信号等。同时,父进程可以同时创建多个子进程,通过不同的 pid 来区分和处理各个子进程。

  1. 一个函数是如何做到返回两次的

fork 函数会创建一个新的进程,即子进程。子进程是父进程的一个副本,它从 fork 函数调用处开始执行,就好像是父进程的一个"分身"。在 fork 函数执行时,内核会为子进程分配资源,并复制父进程的上下文,包括程序计数器、寄存器等。因此,看起来 fork 函数在父进程和子进程中都被执行了一次,也就有了两次返回。一次是在父进程中继续执行,返回子进程的 pid ;另一次是在子进程中执行,返回0。

二者返回存在顺序问题,我们无法表象过去,个人认为父进程优先于子进程返回,因此先输出父进程内容,后输出子进程内容。

  1. 一个变量怎么会有不同的内容?如何理解?

在 fork 之后,父进程和子进程拥有各自独立的地址空间。虽然它们在 fork 之前的变量值是相同的,但 fork 之后,对一个进程中变量的修改不会影响到另一个进程中的同名变量。 这是因为它们的地址空间是相互独立的,每个进程都有自己的一份变量副本。例如,父进程中的一个全局变量 x ,在 fork 之后,父进程和子进程都有自己的 x ,父进程修改自己的 x 不会影响到子进程的 x ,反之亦然。


fork 后父子进程代码共享、数据单独开辟,主要有以下原因:

代码共享(写时拷贝

  • 提高内存利用效率:程序代码通常是只读的,父子进程执行相同的程序代码,若各自复制一份到内存,会浪费大量空间。共享代码段能让多个进程共用同一份代码,减少内存占用。

  • 保证代码一致性:共享代码可确保父子进程执行的代码完全一致,避免因代码复制产生不一致或错误。

数据单独开辟

  • 进程独立性要求:每个进程需有独立数据空间,保证其数据操作不影响其他进程。父子进程后续可能执行不同任务,对数据有不同修改需求,若数据不独立,一个进程对数据的修改会影响另一个进程,导致程序逻辑混乱。

  • 数据安全与稳定:单独开辟数据空间为进程提供了安全稳定的运行环境。一个进程的数据损坏或异常不会波及其他进程,增强了系统的稳定性和可靠性。

  1. fork 函数究竟是什么,干什么

fork 函数是UNIX和类UNIX系统中的一个系统调用,用于创建一个新的进程。它的主要作用是将当前进程(父进程)复制一份,生成一个新的进程(子进程)。子进程在许多方面与父进程相似,包括程序代码、数据段、堆、栈等,但它们是两个独立的进程,有各自的进程控制块(PCB)和独立的地址空间。这样,父进程和子进程可以并发执行,各自执行不同的任务,从而实现多任务处理。例如,一个服务器程序可以通过 fork 函数创建多个子进程来同时处理多个客户端的请求。

进程状态

操作系统学科进程状态(运行,阻塞,挂起)

进程状态与内存管理

在Linux系统中,进程状态丰富多样且与内存管理紧密相连。当进程处于阻塞状态时,往往是在等待特定事件,像从键盘读取数据。此时,进程会被挂起,其关键信息存储于 struct task_struct 结构体中。若操作系统内存资源严重匮乏,部分进程将被换出至交换分区(swap),以此节省内存,保障系统正常运转。待条件满足,比如所需数据准备就绪,进程又会被换入内存继续执行。

struct task_struct 是Linux内核用于描述进程的重要结构体,涵盖进程状态、优先级等众多属性。而 struct dev 则用于描述设备,包含设备类型、状态,以及指向相关进程的指针等信息。部分设备结构体还设有等待队列,用于管理那些等待该设备资源的进程。

进程调度与运行

系统中存在一个由调度器管理的运行队列 ( struct runqueue ) ,该队列有指向队头和队尾进程的指针。调度器的职责是从运行队列里挑选合适的进程,使其能在CPU上运行。进程进入运行队列,意味着它已准备就绪,随时可被调度。

进程在CPU上并非一直运行至结束才释放资源,而是引入了时间片的概念(如图中示例的10ms )。当进程的时间片耗尽,即便尚未执行完毕,也会被调度器从CPU上移除,排至运行队列末尾,等待下一轮调度。这种频繁的进程上下操作,即进程切换,让所有进程的代码在一个时间段内都能得以执行,达成并发执行的效果。比如,即便进程遭遇 while(1); 这样的死循环代码,时间片机制也能确保其他进程有机会获得CPU调度。

补充完善

进程状态转换十分复杂,除阻塞和运行状态外,还有就绪状态、睡眠状态等。就绪状态的进程已万事俱备,只待CPU资源,一旦CPU空闲且被调度器选中,便会转入运行状态。睡眠状态又细分为可中断睡眠与不可中断睡眠,前者可被信号唤醒,后者一般在等待特定I/O操作完成后才会苏醒。

调度器选择进程的依据是不同的调度算法。常见的有先来先服务(FCFS)、短作业优先(SJF)、时间片轮转(RR)、优先级调度等。Linux系统采用的完全公平调度器(CFS)更为复杂高效,它通过统计和比较进程的虚拟运行时间,公平分配CPU时间,防止某些进程长时间得不到调度。

内存管理方面,当内存资源不足时,操作系统在选择换出进程至交换分区时,会综合考量进程活跃度、内存使用量等因素。通常,不活跃进程更易被换出。进程换入内存时,需兼顾内存空间分配与进程需求,保障系统高效运行。此外,操作系统还借助页缓存等缓存机制,降低进程换入换出的性能开销,提升数据访问速度。

linux的维护方法

cpp 复制代码
/*
* 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 资源。

  • S (sleeping):睡眠状态(可中断),等待事件完成(如 I/O 操作),可被信号唤醒。
  • D (disk sleep):磁盘睡眠(不可中断),等待磁盘 I/O 完成,无法被信号唤醒。
  • T (stopped):停止状态,进程被暂停(如收到 SIGSTOP 信号)。
  • t (tracing stop):追踪停止,因调试被追踪器暂停。
  • X (dead):死亡状态,进程已终止,即将被系统移除(内核内部状态,一般不可见)。
  • Z (zombie):僵尸状态,进程已结束但父进程未读取其退出状态,资源未完全释放。**

说明:状态值为对应下标,可通过位运算判断组合状态(如 S+D 对应 1|2=3 )。

Z(zombie)-僵尸进程

僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)

没有读取到子进程退出的返回代码时就会产生僵死(尸)进程

僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。

所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
     pid_t id = fork();
     if(id < 0){
         perror("fork");
         return 1;
     }
     else if(id > 0){ //parent
     printf("parent[%d] is sleeping...\n", getpid());
     sleep(30);
     }
    else{
         printf("child[%d] is begin Z...\n", getpid());
         sleep(5);
         exit(EXIT_SUCCESS);
     }
     return 0;
}

僵尸进程危害

进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!

维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话

说,Z状态一直不退出,PCB一直都要维护?是的!

那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
内存泄漏?是的!

如何避免?后面讲

进程状态总结

至此,值得关注的进程状态全部讲解完成,下面来认识另一种进程

孤儿进程

父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?

父进程先退出,子进程就称之为"孤儿进程"

孤儿进程被1号init进程领养,当然要有init进程回收喽。

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
     pid_t id = fork();
     if(id < 0){
         perror("fork");
         return 1;
     }
     else if(id == 0){//child
         printf("I am child, pid : %d\n", getpid());
         sleep(10);
     }
     else{//parent
         printf("I am parent, pid: %d\n", getpid());
         sleep(3);
         exit(0);
     }
     return 0;
}

进程的删改

kill -9 pid(删除对应进程)

kill -19 暂停进程

kill -18 恢复 暂停进程

进程优先级

什么是优先级:对于资源的访问,谁先访问,谁后访问。

为什么有优先级:****++##因为资源是有限的,进程是多个的,注定了,进程间是竞争关系。++

++##操作系统必须保证大家的良性竞争,确认优先级++

++##如果进程长时间得不到cpu资源,该进程的代码长时间无法得到推进(饥饿问题)++

优先级怎么设定

基本概念

cpu资源分配的先后顺序,就是指进程的优先权(priority)。

优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。

还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

查看系统进程

在linux或者unix系统中,用ps --l命令则会类似输出以下几个内容:

我们很容易注意到其中的几个重要信息,有下:
UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值


PRI and NI

PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高

那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值

PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice

这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行

所以,调整进程优先级,在Linux下,就是调整进程nice值

nice其取值范围是**-20至19,一共40个级别**。

PRI vs NI

需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
可以理解nice值是进程优先级的修正修正数据

在Linux中, nice 和 renice 命令用于调整进程的优先级。以下是它们的用法:
nice命令
**- 功能:用于在启动进程时设置其优先级。优先级的范围是 -20(最高优先级)到 19(最低优先级),默认优先级是 0。

  • 语法: nice [ -n 优先级值 ] 命令 。例如,要以较高优先级(优先级值为 -5)启动一个名为 myapp 的程序,可以使用nice -n -5 myapp。**
    renice命令
    **- 功能:用于调整已经在运行的进程的优先级。
  • 语法: renice [ -n 优先级值 ] -p 进程ID 。例如,如果要将进程ID为 1234 的进程优先级调整为 5,可以使用renice -n 5 -p 1234
    需要注意的是,普通用户只能将进程的优先级调整为较低的值(即优先级数字更大),只有 root 用户可以将优先级调整为较高的值(即优先级数字更小)。**

进程优先级查看操作

ps -l/-al/|grep pid

进程优先级改变操作

top(root用户)

在Linux系统中,使用 top 命令修改进程优先级的操作步骤如下:

前提说明

进程优先级范围是 -20(最高优先级)到19(最低优先级) ,普通用户只能降低自己进程的优先级(即让优先级数值变大),只有root用户能随意调整优先级。

操作步骤

1. 打开 top 界面:在终端输入 top 命令并回车 ,进入 top 交互界面,该界面实时显示系统中进程的运行状态等信息。
2. 选择要修改优先级的进程:在 top 界面中,找到需要修改优先级的进程,记住其PID(进程号,在 PID 列中显示 )。
3. 进入修改优先级模式:在 top 交互界面中,按下字母 r 键 ,此时 top 会提示你输入要调整优先级的进程PID。
4. 输入进程PID:输入目标进程的PID,然后回车 。
5. 设置新的优先级: top 会再次提示你输入新的nice值(即优先级值 )。根据需求输入 -20到19之间的数值,回车确认。比如想提高进程优先级,可输入较小的负数(如 -5);想降低优先级,输入正数(如5 )。
6. 退出 top (可选):完成优先级修改后,若想退出 top 界面,按 q 键即可。

示例:若有个进程PID为1234,想将其优先级调整为 -5 ,在 top 界面按 r ,输入 1234 回车,再输入 -5 回车就能完成调整。 另外,也可使用 renice 命令在 top 外修改进程优先级,格式为 sudo renice 优先级数值 -p 进程PID ( sudo 用于获取root权限 )

相关推荐
好名字更能让你们记住我13 分钟前
Linux多线程(六)之线程控制4【线程ID及进程地址空间布局】
linux·运维·服务器·开发语言·jvm·c++·centos
code monkey.1 小时前
【寻找Linux的奥秘】第九章:自定义SHELL
linux·shell·c/c++
繁华似锦respect1 小时前
SSL/TLS 协议详解:安全通信的基石
linux·服务器·c++·网络协议·安全·visualstudio·ssl
请叫我阿杰1 小时前
Ubuntu取消开机用户自动登录
linux·ubuntu·自动登录
海天鹰2 小时前
Cinnamon开始菜单(1):获取应用数据
linux
chian-ocean2 小时前
Linux线程同步实战:多线程程序的同步与调度
linux·运维·redis
杨了个杨89823 小时前
Centos系统搭建主备DNS服务
linux·运维·centos
Watink Cpper5 小时前
[Redis] Redis:高性能内存数据库与分布式架构设计
linux·数据库·redis·分布式·架构
墨染天姬5 小时前
android平台驱动开发(六)--Makefile和Kconfig简介
linux
?!7146 小时前
Socket编程之TCP套件字
linux·网络·c++·网络协议·tcp/ip