全返利网站建设,深圳喷绘制作公司,附近的小程序怎么开通,做计算机题的网站目录 一、基本概念
二、查看进程
三、系统调用获取进程标示符
1、获取自己的PID
2、获取父进程的PID
四、创建进程
1、初识fork
2、使用fork的方式
五、进程状态
1、阻塞
2、挂起
3、R状态
4、S状态
5、D状态
6、T状态
6.1、kill指令
6.2、暂停进程与继续进程 …目录 一、基本概念
二、查看进程
三、系统调用获取进程标示符
1、获取自己的PID
2、获取父进程的PID
四、创建进程
1、初识fork
2、使用fork的方式
五、进程状态
1、阻塞
2、挂起
3、R状态
4、S状态
5、D状态
6、T状态
6.1、kill指令
6.2、暂停进程与继续进程
6.3、杀死进程
7、X Z 状态 一、基本概念 在大多数课本中对于进程概念的讲解大致是程序的一个执行实例、正在执行的程序等等。这种说法是不够全面的现在我来带领大家更加深入细致的了解进程。在正式学习之前为了大家更好的理解我先来举一个例子。 如果有一个社会人士想要成为你们学校的学生那么他需要满足什么样的条件呢只需要他本人进入到你们的学校在学校内活动就可以了吗这显然是不行的判断一个人是不是这个学校的学生依据的是这个人有没有被学校所管理学校会不会给他排课给他计学分、发毕业证。 所以同样的把一个可执行程序变为进程不仅仅要把该可执行程序加载到内存中还要让这个可执行程序被操作系统所管理。 我们编写生成的可执行程序本质上是一个普通二进制文件储存在磁盘之中。当我们使用 ./ 运行该可执行程序时会先把该文件的代码和数据加载到内存中做到这一步就相当于一个社会人士本人进入到了学校之中但是这样就叫做进程了吗显然不可能这些代码和数据还需要被操作系统所管理。那么操作系统如何管理被加载到内存中的数据呢遵循我们之前文章中提到的六个字原则先描述再组织。 每一个进程在加载到内存之时操作系统会在内核之中创建一个数据结构对象这个数据结构叫做 PCBprocess control block在Linux操作系统下的PCB是 task_struct 。这些结构体对象提取并填充了对应进程的属性并且每个结构体对象里都有一个指向其对应代码和数据的指针。这就是先描述的过程。 这样随着多个进程加载到内存中操作系统内核里也就有了多个描述结构体这些结构体都叫 PCB并以特定的数据结构连接起来。这就是再组织的过程。 此后所有对于进程的管理都被转换成了对数据结构PCB的增删查改这是一个对进程的管理建模的过程。
总结进程是内核关于进程的相关数据结构与当前进程的代码和数据的结合。很多课本中着重的强调了当前进程的代码和数据的部分而忽略掉了内核中相关数据结构的部分。 由于 Linux 是使用C语言写的所以Linux操作系统下的 task_struct 就是结构体。 我们知道文件包括内容和属性那么在操作系统中为了管理进程所创建的PCB中的进程属性与磁盘中文件的属性有关联吗有关联但是关联不大有关联的部分包括文件属性中的权限、名称、大小等等但大部分的属性是没有关联的。
PCB结构体是一种内核数据结构是由操作系统重起炉灶创建和维护的里面的进程属性和磁盘文件的属性基本没有关系。它会被装载到RAM(内存)里并且包含着进程的属性信息
标示符: 描述本进程的唯一标示符用来区别其他进程。状态: 任务状态退出代码退出信号等。优先级: 相对于其他进程的优先级。程序计数器: 程序中即将被执行的下一条指令的地址。内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针上下文数据: 进程执行时处理器的寄存器中的数据[休学例子要加图CPU寄存器]。I O状态信息: 包括显示的I/O请求,分配给进程的I O设备和被进程使用的文件列表。记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。其他信息
二、查看进程
我们先编写一个程序 并把它编译形成一个可执行文件。此时我们使用 ./ 来运行这个可执行文件就会自动创建一个进程。 查看进程指令
ps axj
查看当前所有系统中的进程 如果我们只想看我们自己的进程则可以使用 grep 来进行过滤
ps axj | grep [进程名] 此时就可以看到有一个 myprocess 进程在进行至于下面第二行中的 grep -colorautomyprocess字样则是因为我们在系统之中查找进程时由于 grep 文本过滤自己也是一个进程就会导致自己把自己也给过滤出来了并且显示在下面如果不想看到这一行可以通过指令 grep -v grep 来避免显示。 现在我们再启动一次这个可执行程序并且观察进程 这时就可以看到两个进程在进行了。我们观察这两个进程 可以发现其中一个属性 PID 是不同的这说明同一个可执行程序被启动了两次所产生的是两个不同的进程。换言之把一个可执行程序多次加载到内存中会存在多个进程。 除了使用 ps 命令查看进程之外还有一种方式可以查看进程那就是查看 proc 目录。proc 目录是一个内存级的文件系统只有当操作系统启动的时候它才会存在在磁盘上并不存在 proc 目录。我们可以通过该目录查到内存相关的信息 这一大堆蓝色的都是目录名称其中蓝色数字就是系统中特定进程的 PID 我们进入其中一个以新增进程的 PID 命名的目录就可以看到所启动的进程相关的属性了 其中有两个属性我们比较熟悉一个是可执行程序对应的路径一个是可执行程序的名称。这就是为什么说管理进程所创建的PCB中的进程属性与磁盘中文件的属性有小部分关联就体现在这里。 现在我们把两个新增进程终止掉
Ctrl c 此时我们再在 proc 目录中查看以进程 PID 命名的目录时就会提示进程不存在 所以 proc 目录里的内容是动态变化的。
三、系统调用获取进程标示符
1、获取自己的PID
获取进程自己的PID的系统调用
getpid
通过 man 指令来查看 getpid 通过函数说明我们得知谁调用这个函数就获取谁的PID。
现在我们通过编写程序来更加直观的感受一下 运行生成的可执行程序 就可以直接看到该进程的PID了。现在我们使用 Ctrlc 终止该进程然后再重新运行可执行文件生成新的进程再来观察一下 可以看到每次重新启动进程PID都不一样这是很正常的因为每一次进程在加载启动的时候操作系统就创建PIDPID是操作系统来维护的线性递增。
2、获取父进程的PID
获取父进程的PID的系统调用
getppid
重新编写代码 运行生成的可执行程序 可以看到该进程的PID与PPID现在我们使用 Ctrlc 来终止该进程并重新运行可执行程序生成新的进程 可以发现进程的PID在不断的变化而PPID却没有变化那么PPID是谁呢
我们通过 ps 指令查看该进程 发现这个父进程其实是 bash 即命令行解释器。
结论
bash命令行解释器本质上也是一个进程命令行启动的所有的程序最终都会变成进程而该进程对应的父进程都是bash
那么 bash 为什么要创建子进程来执行程序呢这是为了防止我们执行的程序发生错误如果 bash 自己来执行程序如果程序挂了那么 bash 也就挂了这是相当危险的事情。
四、创建进程
1、初识fork 在以前我们熟悉的创建进程的方式有两种第一种是在Windows系统下我们双击一个 .exe 文件就创建了一个进程还有一种是在Linux系统下我们通过在命令行输入 ./ 来将程序变成进程去运行。
现在我们再来学习一种创建进程的方式通过系统调用
fork
我们通过实际操作来学习如何使用 fork
编辑程序 运行生成的可执行程序 发现打印了一行A却打印了两行B这是什么原因呢
我们修改一下程序输出打印B这一行函数的进程与父进程 运行可执行程序 发现这两行打印的进程不同这可以说明这两行是两个不同的进程之后又发现第二行打印的父进程刚好是第一行打印的进程这说明这两个进程是父子关系。此时我们就完成了创建子进程的操作。那我们如何控制父进程与子进程呢
通过查找 man 手册我们来研究一下 fork 函数 手册说明fork 的返回值类型是 pid_t即有符号整数。进程创建成功子进程的PID会返回给父进程0 会返回给子进程。进程创建失败-1 会被返回给父进程。
我们依旧通过实际操作来理解 运行可执行程序 这时观察到了一个奇怪的现象打印两次 ret 的值不同为什么一个函数会有两个返回值呢这两个 ret 的地址相同说明他们是同一个变量但是为什么打印出了两个不一样的值呢 首先我们需要知道 fork 做了什么进程 内核数据结构 进程的代码和数据当我们创建子进程的时候并不是把代码和数据又拷贝了一份而是在内核中再创建一个子进程PCB子进程PCB的大部分属性会以父进程PCB为模板并把属性信息拷贝进来。 父进程的PCB指向自己的代码和数据子进程PCB也指向同样的代码和数据。所以 fork 就相当于在内核中创建独立的PCB结构并让子进程与父进程共享一份代码和数据。 进程在运行的时候是有独立性的任何一个进程出现故障不会影响其他进程父子进程运行的时候也是一样的。 代码代码是只读的所以进程无法修改代码也就无法相互影响 数据当有一个执行流尝试修改数据的时候OS会自动给当前进程触发一个机制写时拷贝简单来说就是在写入的时候OS会把该数据重新拷贝一份此时写入、修改就在这个备份上执行而不会修改原始数据。从而在数据上也能保持无法相互影响。 有了以上的知识储备我们再来研究 fork 如何拥有两个返回值。我们知道当一个函数准备执行 return 语句的时候该函数的主体功能就已经完成了return 语句不影响函数的功能仅仅起到返回结果的作用。因此 fork 系统调用函数在执行 return 语句之前子进程就已经创建完成并已经在进行中了所以当执行 return 语句返回结果的时候就要给父进程与子进程各自返回一份结果即执行了两次。最终返回结果被赋值给变量 ret 的时候OS自动触发了写时拷贝分别把结果存入两者的备份空间中。至于为什么打印出来的 ret 的地址是相同的这与虚拟地址有关下面会讲。
总结
fork有两个返回值父子进程代码共享数据各自开辟空间私有一份采用写时拷贝
2、使用fork的方式
一般情况下我们使用 fork 创建子进程之后通常要用 if 进行分流 编译运行 fork之后执行流会变成两个执行流谁先运行由调度器决定。父子进程通过 if 分流分别执行不同的代码块。
五、进程状态 进程在CPU上运行的时候并不是一直在运行的而是一个进程先在CPU上运行一会再切换另一个进程在CPU上运行一会不断的切换进程周而复始重复运作的。这叫做基于进程切换的分时操作系统由于CPU的运行速度非常快切换速度使人类感觉不到从而使人们有种进程一直在运行的感觉。而CPU会去调用哪一个进程是由进程的状态来决定的一个进程可以有多个状态我们先来说明两个最为核心的状态阻塞和挂起。
1、阻塞 进程因为等待某种条件就绪而导致的一种不推进的状态叫做阻塞状态给人们最直观的感受就是程序卡住了。换句话说一个进程阻塞一定是在等待某种所需要的资源就绪的过程。 想象这样一个场景我们在下载一些资料的时候如果网断了CPU还有必要继续调度这个下载进程吗肯定是没必要了因为没有意义此时就会把该进程设置为阻塞状态。那么这个进程是如何等待网络资源就绪的呢 我们之前讲过操作系统要管理网卡、磁盘等外设是一个先描述再组织的过程操作系统创建多个结构体类型这里命名为 struct dev 并把各个外设的属性信息提取填充进来再用对应的数据结构把他们链接到一起。同样操作系统管理大量的进程也是一个先描述再组织的过程。 当网络断开时 需要等待网络资源的进程就会把自己的PCB从CPU的某些特定队列中拿取出来连接到网卡设备结构体队列的尾部来排队等待网络资源 此时再获取到等待的资源之前该进程不会再被CPU调度。 PCB是可以被维护在不同的队列中的。进程在等待哪种资源就会被排列到哪种资源的队列中去。再举个例子当我们在C语言中使用scanf 函数时运行程序如果我们不在键盘上输入内容进程就会处于阻塞状态并在键盘的结构体中排队等待资源只有拿到数据时进程才会再次被CPU调度。
总结阻塞就是不被CPU调度——一定是因为当前进程需要等待某种资源就绪——一定是进程tesk_struct结构体需要在某种被OS管理的资源下排队。
2、挂起 如果有时候出现了内存资源紧张的情况而且阻塞进程的PCB被接入到了所需要等待资源的结构体队列中不被调度。这时操作系统就会把阻塞进程的代码和数据交换到磁盘中同时释放其所在内存中占据的空间从而起到节省内存空间的目的。等到进程所需要的资源就绪的时候再把该进程的代码和数据加载到内存中交由CPU调度。 其中把进程的代码和数据由OS暂时性的交换到磁盘中时称该进程处于挂起状态。全称为阻塞挂起状态。 以下是Linux内核源码中摘抄下来的进程状态
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 */
};3、R状态
当一个进程被加载运行时该进程处于 R 状态。但是进程是 R 状态并不一定代表其一定在CPU上运行。 一个进程是什么状态一般也看这个进程在哪里排队。 其中在CPU的运行结构体队列中排队等待调度的进程都是运行状态即 R 状态。在其他资源结构体队列中排队的进程都是阻塞状态。
现在我们写一个程序来学习进程的各种状态 编译运行 进程明明在运行为什么却显示成 S 进制状态呢
现在我们修改一下代码把打印函数 printf 注释掉 再来编译运行 此时进程的状态就是 R 状态了。 出现这种情况的原因是 printf 函数是向外设打印消息而外设并不是随时准备就绪的也就是说进程在执行 printf 函数时需要在外设的结构体队列中排队等待当外设资源就绪时该进程才能被CPU调度变为 R 状态。其他时间都是阻塞状态 S 状态就是一种阻塞状态。
4、S状态 S 状态是休眠状态本质上是一种阻塞状态。可以按下 Ctrlc 终止进程。
5、D状态 D 状态也是一种休眠状态不可被中断休眠。 在一些进程极多、内存压力极大的情况下OS是有权利杀掉休眠状态的进程以腾出空间保证其他进程正常运行的这也是十分合理的。 但是在有一种情况下这种权力变成了不合理那就是这个休眠的进程正在磁盘区排队向磁盘存入数据如果这个时候OS把该进程杀掉了就会导致磁盘存储数据出错万一这个数据还特别重要就会造成非常严重的后果。 为了解决这个问题就设计出了 D 状态处于 D 状态的进程无法被OS杀死甚至在系统中存在 D 状态的进程时计算机都没有办法正常关机。只有当 D 状态的进程自己苏醒的时候这个进程才能被结束。 事实上一般情况下不会出现 D 状态的进程的D 状态进程一旦出现就说明磁盘的空间已经非常的紧张存储速度非常的慢了需要力保写入数据的进程活着完成任务长时间内不能被OS杀死。既然OS都已经需要主动杀死休眠的进程并且磁盘资源已经不够了可见此时内存的情况也好不到哪里去。当系统中出现了一个 D 状态的进程就离计算机宕机不远了。
6、T状态 T 状态名为暂停状态也是一种阻塞状态。我们在调试程序时让程序在断点处停下来本质上就是让进程暂停
6.1、kill指令
查看 kill 指令
kill -l 在这里主要使用编号为 9、18、19 的命令选项功能分别为 杀死进程、继续进程、暂停进程。
6.2、暂停进程与继续进程
我们先运行进程并查看进程状态 观测到的是 S 状态但实际上程序已经运行了现在使用指令
kill -19 [进程PID]
来暂停进程 此时进程状态就已经变成了 T 。
接着使用指令
kill -18 [进程PID]
使进程继续进行 观察到进程状态变回了 S 。
6.3、杀死进程 使用 kill 指令恢复进程后可以发现进程状态从原来的 S 变为了 S 并且使用 Ctrlc 已经没有办法结束进程了 进程状态的 号表示前台运行没有 号就表示后台运行 Ctrlc 只能结束前台运行的进程。
此时我们需要使用指令
kill -9 [进程PID]
杀掉进程 7、X Z 状态
一般我们再写 main 函数时会在最后写一个 return0这叫做进程退出码我们使用以下指令可以查到进程退出码
echo $? 如果一个子进程结束时立刻退出父进程是没有机会拿到退出结果的。所以在Linux中进程退出时一般不会立即彻底退出而是要维持一个 Z 状态也叫僵尸状态方便后续父进程读取该子进程的退出结果。 我们编写以下程序 运行并查看进程 可以看到父子进程都已经在运行之中只不过显示出来的是 S 状态。
现在我们杀掉子进程再来观察子进程的状态 此时子进程变为 Z 状态即僵尸状态。
如果我们不去主动回收 Z 状态的进程那么该进程就会一直存在操作系统就会一直维护该进程的PCB占据内存的空间可以理解为内存泄漏所以僵尸进程必须要回收具体回收的方法以后会详细讲解。 关于进程概念的相关内容就讲到这里希望同学们多多支持如果有不对的地方欢迎大佬指正谢谢