网站备案名字填写,番茄todo社区视频免费看,成都app,兰州关键词网络推广#x1f31f;#x1f31f;作者主页#xff1a;ephemerals__
#x1f31f;#x1f31f;所属专栏#xff1a;Linux
目录 前言
一、什么是进程
二、task_struct的内容
三、Linux下进程基本操作
四、父进程和子进程
1. 用fork函数创建子进程
五、进程状态
1. 三种重…
作者主页ephemerals__
所属专栏Linux
目录 前言
一、什么是进程
二、task_struct的内容
三、Linux下进程基本操作
四、父进程和子进程
1. 用fork函数创建子进程
五、进程状态
1. 三种重要状态
运行状态
阻塞状态
挂起状态
2. 内核链表的理解
3. Linux的进程状态
孤儿进程
总结 前言 在学习 Linux 操作系统的过程中进程是一个至关重要的概念。无论你是想了解系统的基础操作还是深入研究 Linux 内核进程管理的理解都将为你打下坚实的基础。进程不仅是操作系统资源管理的核心也是实现多任务处理的关键所在。通过学习进程的创建、调度、同步等机制你可以更好地掌握操作系统的运行原理进而优化系统性能和解决实际问题。本文将从基础知识入手带领大家逐步深入探索 Linux 中进程的各个方面帮助你在 Linux 学习的道路上迈出坚实的第一步。
一、什么是进程 进程有多种描述方式例如程序的运行实例、正在执行的程序、操作系统进行资源调配的基本单位等。不过以上说法都太理论化我们用程序运行的实际情况来描述进程。 一个程序在执行前其二进制代码和数据变量、常量、堆栈数据等需要加载到内存。当加载完成之后操作系统就会为这一块代码和数据创建一个对应的PCB也叫做进程控制块本质是一个存储进程相关信息的结构体其中存在一个内存指针指向代码和数据便于访问。 所以“进程”不仅仅包括了程序的运行实例它也包括操作系统管理该进程的相关信息。简而言之“进程”是指PCB与程序代码数据的集合。操作系统根据PCB来跟踪进程的执行状态方便对进程进行调度。 而当有多个程序需要执行时操作系统就会为每一个程序的代码和数据都创建一个对应的PCB描述过程再通过容器将所有的PCB串联起来组织过程。此时操作系统对于进程的管理即为对容器的增删查改。
需要注意 在Linux下PCB进程控制块是一个叫做task_struct的结构体进程的所有属性都可以通过task_struct直接或间接地找到。 Linux下的task_struct之间通过双向链表进行连接。 二、task_struct的内容 task_struct有如下成员用于表示进程各种状态信息以及访问程序的代码和数据 进程标识符(PID)--区别其他进程 进程状态信息 优先级 程序计数器 内存指针--指向代码和数据 上下文数据 I/O状态信息 记账信息 其他信息 之后的进程学习当中我们将围绕以上成员数据学习进程的相关概念及操作。
三、Linux下进程基本操作 C语言函数获取当前进程标识符和父进程的标识符PID
getpid(); //返回当前进程标识符返回值类型是pid_t
getppid(); //返回当前进程的父进程标识符
注意使用以上函数时需要引头文件unistd.h。 使用指令查看当前所有进程
ps ajx
ls /proc 根据程序名查看某个进程信息
ps ajx | head -1 ps ajx | grep (可执行程序名) 根据标识符查看进程文件
ll /proc/(标识符)
示例 我们可以重点关注一下图中列举出的两个文件cwd和exe
cwd指的是当前进程对应的可执行程序所在目录
exe指的是当前进程对应的可执行程序位置。 C语言函数修改当前进程所在路径
chdir(路径);
注意使用该函数要引头文件unistd.h。 杀进程的两种方式
1. ctrl c
2. 命令行输入kill -9 进程标识符
四、父进程和子进程 一个进程通过系统调用创建出的另一个进程称之为该进程的子进程反之该进程称为其父进程。在Linux下我们在命令行输入的命令都是Bash命令行解释器的子进程。
1. 用fork函数创建子进程 fork是一个系统调用存在于头文件unistd.h中当执行fork函数之后当前进程会创建一个子进程后续的代码会被父进程和子进程分别执行一次。
代码示例
#include stdio.h
#include unistd.hint main()
{fork();printf(hello world\n);return 0;
}运行结果 注意fork函数创建的子进程没有自己的代码和数据虽然操作系统为其创建了PCB但是其内存指针指向的还是父进程的代码和数据。 子进程在创建成功后fork函数会给子进程返回0给父进程返回子进程的PID。为什么会给父子进程不同的返回值呢因为一个父进程可能会有多个子进程给父进程返回子进程的PID更方便父进程对子进程进行管理。而子进程如果想要知道父进程的PID直接调用getppidh函数即可。另外返回值不同可以配合分支语句让父子进程执行不同的代码。示例如下
#include stdio.h
#include unistd.hint main()
{pid_t id fork();if(id 0)//子进程{printf(我是子进程我的pid是%d\n, getpid());}else//父进程{printf(我是父进程我的pid是%d\n, getpid());}return 0;
}
运行结果 那么为什么fork函数能够做到返回两个值呢实际上fork函数在执行return语句之前就已经创建好了子进程此时就可以通过分支语句来区分给父进程和子进程的返回值。 注意虽然fork函数创建的子进程与父进程的代码是共享的但如果父子任何一方要修改其中的数据那么操作系统就会将数据进行拷贝此时父子就各自维护自己的数据本质上修改的是拷贝的数据不会影响另一方。这种状况叫做写时拷贝。 五、进程状态 对于不同的操作系统进程状态可能略有不同但常见的大体上的进程状态有如下几种创建、就绪、运行、阻塞、终止、挂起。我们介绍一下其中最重要的三点运行状态、阻塞状态和挂起状态。
1. 三种重要状态
运行状态 首先要知道一般情况下一个CPU维护一个进程调度队列该队列中存放着一个个PCB等待CPU对它们进行调度。而一个PCB在运行队列中排队时就称该进程处于运行状态。
阻塞状态 当一个进程需要等待某种资源或设备如鼠标、键盘等就绪时该进程就处于阻塞状态。阻塞状态的进程在代码层面的体现是PCB从运行队列中移出转而进入设备的等待队列当中。
此时若设备准备就绪如按下键盘则操作系统会修改当前设备状态然后检查等待队列将等待队列中的PCB重新移动到运行队列当中该进程重新恢复运行状态。
挂起状态 当一个进程被暂停执行时称该进程处于挂起状态。 那么它的具体体现是什么呢 当内存空间较为吃紧时操作系统会将一些暂时不需要使用的内存数据如阻塞状态的PCB控制的代码和数据唤出到磁盘中的swap交换分区。此时等待队列中的PCB不再维护该进程的代码和数据这样的进程状态叫做阻塞挂起。 此时若设备准备就绪则操作系统就将swap交换分区中的代码和数据重新唤入到内存中给PCB维护然后恢复到运行状态。 当内存空间严重不足时操作系统会将运行状态的PCB控制的代码和数据也唤出到swap交换分区。此时称之为运行挂起。 由这三种状态在代码层面的一部分具体体现我们可以得出如下结论进程状态的变化表现之一就是PCB在不同的数据结构之间移动变化本质是操作系统对数据结构的增删查改。
2. 内核链表的理解 之前提到在Linux下操作系统会使用双向链表将PCB串联起来方便进程管理。那么为什么PCB还会出现在CPU维护的调度队列当中呢其实task_struct确实是同时出现在两种数据结构当中的它基于一种特殊的结构来实现 task_struct当中将用于构成双向链表的指针域封装成一个结构体list_head它的指针指向的是其他task_struct的list_head。那么既然指向另一个指针域如何能访问到task_struct的其他成员呢这就需要用到结构体内存对齐的相关知识了结构体的成员都是按照自身的对齐数进行存储的第一个成员变量的地址就是结构体的首地址。通过求出list_head相对于结构体第一个成员的偏移量就能间接访问结构体的其他成员。例如如下表达式就可以表示next指针指向的list_head所在task_struct的首地址其中links表示list_head的变量
(struct task_struct*)(next - ((struct task_struct*)0-links))
将0强转为task_struct*类型求出成员links的地址即为links的偏移量然后用links的地址减去该偏移量得出task_struct的首地址再强转为task_struct*类型然后就可以访问其他成员了。 而其他指针域也可以通过这种方式访问task_struct的其余成员但可以用不同的链接方式形成不同的数据结构这样就实现了一个PCB同时存在于多种数据结构的壮举。
3. Linux的进程状态 相比于之前提到的操作系统大体上的进程状态Linux的进程状态就显得更加具体化。在Linux下进程状态本质是task_struct内的长整型变量它有以下几种进程状态表示
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运行状态
S休眠状态可中断休眠
D深度睡眠状态不可中断休眠 进程处于深度睡眠状态时不可被杀。 T暂停状态--用户手动暂停进程如Ctrl z
t追踪状态--调试过程中执行到断点处进程被暂停
x死亡状态
z僵尸状态--子进程在死亡之后代码和数据可以释放但其PCB不能直接释放需要被父进程读取信息读取信息之前称之为僵尸状态。 注意如果父进程一直都不读取子进程的信息那么僵尸状态就会一直存在PCB也会一直存在这就导致了内存泄漏。 孤儿进程 除了以上几种状态进程还有一种特殊情况孤儿进程。 当父进程先死亡子进程就会被1号进程领养成为新的父进程此时该子进程就被称作孤儿进程。 注1 号进程init 或 systemd 是 Linux 系统中的第一个用户态进程负责初始化系统并管理其他进程。它由内核在系统启动时创建PID固定为 1。现代 Linux 主要使用 systemd 作为 1 号进程提供服务管理、日志收集和系统控制功能而早期系统则使用 sysvinit 或 upstart。如果 1 号进程崩溃系统通常会进入不可用状态需要重启。 那么为什么子进程会被1号进程领养呢如果1号进程不领养它则当子进程死亡后没有父进程读取信息就会造成内存泄漏。
总结 通过本篇文章我们学习了Linux进程的基础知识包括进程概念、task_struct 结构、进程状态以及父子进程关系希望这篇文章能帮助你更清晰地理解Linux进程的运行机制。如果你觉得博主讲的还不错就请留下一个小小的赞在走哦感谢大家的支持❤❤❤