Linux进阶-加深进程印象

目录

进程

进程状态转换

进程状态

启动新进程

system()函数

system.c文件

Makefile文件

执行过程

fork()函数

函数原型

fork.c文件

Makefile文件

执行过程

exec系列函数

函数原型

execl.c文件

Makrfile文件

执行过程

终止进程

exit()函数和_exit()函数 

头文件和函数原型

等待进程

wait()函数

头文件和函数原型

wait.c文件

Makefile文件

执行过程

waitpid()函数

头文件和函数原型


进程

进程状态转换

一般来说,一个进程的开始都是从其父进程调用fork()函数开始,所以在系统一上电运行时,init进程就开始工作在系统运行过程中,会不断启动新的进程(要么由init进程启动,要么由被init进程启动的其他进程所启动)init进程的PCB是从内核的启动镜像文件中直接加载的,系统中的所有其他进程都是init进程的后代

一个进程被启动后都是处于可运行状态(但此时进程并未占用CPU运行),处于该状态的进程可以是正在进程等待队列中排队(就绪态),也可以占用CPU正在运行(运行态)。

系统产生进程调度时,处于就绪态的进程可以占用CPU的使用权,处于运行态。但每个进程运行时间是有限的(时间片),当进程的时间片已经耗光,如果进程还没有结束运行,那么会被系统重新放入等待队列中等待,处于就绪态,等待下一次进程的调度。另外,正处于运行态的进程即使时间片没有耗光也可能被别的更高优先级的进程抢占被迫重新回到等待队列中等待

处于运行态的进程可能会因为等待某些事件、信号或资源而进入可中断睡眠态(比如进程要读取一个管道文件数据而管道为空时,或进程要获得一个锁资源而当前锁不可获取时,甚至是进程自己调用sleep()函数来强制将自己进入睡眠等)。

可中断睡眠态:可以被中断的,能响应信号的睡眠状态。在特定条件发生后,进程状态就会转变为“就绪态”(比如其他进程向管道文件写入数据,或锁资源可以被获取了,或睡眠时间到达等)。

处于运行态的进程也可能会进入不可中断睡眠态,即进程不能响应信号。但这种状态非常短暂,我们几乎无法通过ps命令将其显示,一般处于这种状态的进程都是在等待输入或输出(I/O)完成,在等待完成后自动进入就绪态。

当进程收到SIGSTOP或SIGTSTP中的其中一个信号时,进程状态会被置为暂停态不再参与调度,但系统资源不会被释放直到收到SIGCONT信号后被重新置为就绪态。

当进程被追踪时(常见是使用调试器调试应用程序时)收到任何信号状态都会被置为TASK_TRACED状态,不再参与调度,但系统资源不会被释放直到收到SIGCONT信号后被重新置为就绪态。

进程在完成任务后会退出,那么此时进程状态为退出态(属于正常退出,如main()函数return,或调用exit()函数,或线程调用pthread_exit()函数)。

不正常退出时,那么此时进程状态为僵尸进程(如进程收到kill信号)。其实不管怎么死,内核都会调用do_exit()函数来使进程状态变为僵尸进程。

僵尸进程的僵尸指的是进程的进程控制块PCB。为什么一个进程死掉之后还要把PCB留下呢?因为进程在退出时,系统会将其退出信息都保存在PCB中(比如死亡原因),得以让父进程去排查(父进程之所以要启动该进程,很大原因是要让进程去干某一件事情,当该进程死亡,父进程当然要知道那一件事情办得怎样)

父进程去处理僵尸进程时,会将这个僵尸进程的状态设置为EXIT_DEAD,即退出态,系统才能去回收僵尸进程的内存空间,否则系统将存在越来越多的僵尸进程,最后导致系统内存不足而崩溃。

当父进程由于太忙而没能及时去处理僵尸进程时,可以考虑使用信号异步通知机制(让一个孩子在变成僵尸时给其父进程发一个信号,父进程接收到这个信号后再对其进行处理)。

当父进程先一步于子进程退出时,子进程将变成孤儿进程(没有父进程),孤儿进程将被祖先进程(init)收养。所以当孤儿进程退出时,init进程将回收资源。

进程状态

执行ps -ux可查出进程的状态。

状态说明
R

可运行状态。表示进程在运行队列中,处于正在运行或即将运行的状态。

只有在该状态才可能在CPU上运行,同一时刻可能有多个进程处于可运行状态

S可中断睡眠态。处于这个状态的进程可能因为等待某种事件的发生而被挂起,比如进程在等待信息
D不可中断睡眠态。通常是在等待输入或输出(I/O)完成,处于这种状态的进程不能响应异步信号
T停止态。通常是被Shell的工作信号控制,或因为处于调试器的控制下进程被追踪
Z退出态。进程成为僵尸进程
X退出态。进程即将被回收
s进程是会话其首进程
l进程是多线程的
+进程属于前台进程组
<高优先级任务

启动新进程

system()函数

简单,但效率低下而且具有不容忽视的完全风险。

system.c文件
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main(void)
{pid_t result;result = system("ls -l");return result;
}

Makefile文件
ARCH?=x86
ifeq ($(ARCH), x86)CC = gcc
elseCC = arm-linux-guneabihf-gcc
endifTARGET=system
BUILD_DIR=build
SRC_DIR=module
INC_DIR=include
CFLAGS = $(patsubst %,-I %,$(INC_DIR))
INCLUDES = $(foreach dir, $(INC_DIR), $(wildcard $(dir)/*.h))SOURCES = $(foreach dir, $(SRC_DIR), $(wildcard $(dir)/*.c))
OBJS = $(patsubst %.c, $(BUILD_DIR)/%.o, $(notdir $(SOURCES)))
VPATH = $(SRC_DIR)$(BUILD_DIR)/$(TARGET):$(OBJS)$(CC) $(^) -o $(@)
$(BUILD_DIR)/%.o:%.c $(INCLUDE) | create_build$(CC) -c $< -o $@ $(CFLAGS).PHONY:clean create_build
clean:rm -r $(BUILD_DIR)
create_build:mkdir -p $(BUILD_DIR)

执行过程

fork()函数

复杂,但提供更好地弹性、效率和安全性。

fork()函数用于从一个已存在的进程(父进程)中启动一个新进程(子进程)。父进程的fork()调用返回的是新子进程的PID新子进程的fork()调用返回的是0

使用fork()函数的本质是将父进程的内容复制一份,但是有一些具体区别。

子进程与父进程一致的内容有:

进程的地址空间

进程上下文、代码段

进程堆、栈空间,内存信息

进程的环境变量

标志IO的缓冲区

打开的文件描述符

信号响应函数

当前工作路径

子进程独有的内容有:

进程号PID。

记录锁。父进程对某文件加了把锁,子进程不会继承这把锁。

挂起的信号。这些信号是已经响应但尚未处理的信号(悬挂的信号),子进程也不会继承这些信号。

因为子进程几乎是父进程的完全复制,所以父子进程会运行同一程序,但资源和时间都会消耗很大。

当发出fork()系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程。这很耗时,因为需要做一些事情:

        为子进程的页表分配页面

        为子进程的页分配页面

        初始化子进程的页表

        把父进程的页复制到子进程相应的页中

创建一个地址空间的方法涉及许多内存访问,消耗许多CPU周期,并且完全破坏了高速缓存的内容,因此直接复制物理内存对系统的开销会产生很大的影响,更重要的是在大多数情况下,这样直接拷贝通常是毫无意义的,因为许多子进程通过装入一个新的程序开始它们的执行,这样就完全丢弃了所继承的地址空间。因此在Linux中引入一种写时复制技术(COW)

Linux系统中的进程都是使用虚拟内存地址,虚拟地址和真实物理地址之间是有一个对应关系的,每个进程都有自己的虚拟地址空间,而操作虚拟地址明显比直接操作物理内存更加便捷快捷,所以写时复制技术是一种可以推迟甚至避免复制数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间(页面)。

写时复制的思路在于:父进程和子进程共享页面而不是复制页面。共享页面不能被修改,无论父进程和子进程何时试图向一个共享的页面写入内容时,都会产生一个错误,这时内核就把这个页复制到一个新的页面中并标记为可写。原来的页面仍然是写保护的,当还有进程试图写入时,内核会检查这个试图写入的进程是否是这个页面的唯一宿主,如果是则把这个页面标记为对这个进程是可写的。

总的来说,写时复制技术只会用在需要写入时才会复制地址空间,从而使各个进程进行拥有各自的地址空间在此之前父进程和子进程都是以只读方式共享页面,这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。而在绝大多数时候共享页面根本不会写写入,例如在调用fork()函数后立即执行exec()函数,地址空间就无需被复制了,这样子fork()的实际开销就是复制父进程的页表和给子进程创建一个进程描述符。

函数原型
pid_t fork(void);

在fork()函数启动新进程后,子进程和父进程开始并发执行(谁先执行取决于内核调度算法决定)。

fork()函数如果启动新进程成功,会对父子进程各返回一次,对父进程返回子进程的PID,对子进程返回0;

fork()函数如果启动新进程失败,将返回-1。失败的原因通常是因为父进程所拥有的子进程数目超过了规定的限制(CFILD_MAX),此时errno变量将被设为EAGAIN。如果是因为进程表里没有足够的空间用于创建新的表单或虚拟内存不足,errno变量将被设为ENOMEM。

fork.c文件
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main(void)
{pid_t result;result = fork();if(result == -1){printf("fork error!\n");}else if(result == 0){printf("the result value:%d, child process pid:%d\n", result, getpid());}else{printf("the result value:%d, father process pid:%d\n", result, getpid());}return result;
}

Makefile文件

照旧

执行过程

exec系列函数

事实上,使用fork()函数启动一个子进程是并没有太大作用的,因为子进程和父进程是一样的(子进程能干的活父进程也能干),因此就想让子进程做不一样的事情,于是诞生了exec系列函数,主要用于替换进程的执行程序

exec系列函数可以根据指定的文件名或目录名找到可执行文件(二进制文件,或可执行脚本文件),并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完后原调用进程的内容除了PID外,其他全部被新程序的内容替换

简单来说,就是覆盖进程。如A进程通过exec系列函数启动B进程,此时B进程会替换A进程,A进程的内容空间、数据段、代码段等内容都将被进程B占用,然后进程A将不复存在。

函数原型
int execl(const char *path, const char *arg, ...);    // NULL结束
int execle(const char *path, const char *arg, ..., char *const envp[]);int execlp(const char *file, const char *arg, ...);   // NULL结束
int execvp(const char *file, char *const argv[]);int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

execl.c文件
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main(void)
{int err;err = execl("/bin/ls", "ls", "-al", NULL);if(err < 0){printf("execl fail\n");}return 0;
}

Makrfile文件

照旧

执行过程

调用execl函数,这个函数在/bin/ls目录中搜索程序ls,然后它将会替换execl.c本身的进程。

当调用exec系列函数后,当前进程将不会再继续执行。一般情况下,exec系列函数是不会返回的,除非发生了错误。出现错误时,exec系列函数将返回-1,并且会设置错误变量errno。

终止进程

正常终止

        从main函数返回

        调用exit()函数终止

        调用_exit()函数终止

异常终止

        调用abort()函数异常终止

        由系统信号终止

exit()函数和_exit()函数 

在linux系统中,exit()函数定义在stdlib.h中,而_exit()定义在unistd.h中。当程序执行到exit()或_exit()函数时,进程会无条件地停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止当前进程的允许。

_exit()函数:简单,直接通过系统调用使进程终止运行,在终止进程时会清除这个进程使用的内存空间,并销毁它在内核中的各种数据结构。

exit()函数:在终止进程前会检查文件的打开情况,把文件缓冲区的内容写回文件。调用后会变成僵尸进程。

僵尸进程放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集。 

缓存IO操作:在Linux的标准函数库中使用,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区中读取;同样,每次写文件的时候也仅仅是写入内存中的缓冲区,等满足了一定的条件(如达到一定数量或遇到特定字符等),再将缓冲区的内容一次性写入文件。

头文件和函数原型
#include <unistd.h>
void _exit(int status);#include <stdlib.h>
void exit(int status);
// status表示进程终止时的状态码,0表示正常终止,其他非0值表示异常终止(一般使用-1或1)
// 标准C里使用EXIT_SUCCESS和EXIT_FAILURE宏表示正常终止和异常终止

等待进程

当父进程希望知道子进程何时结束或子进程结束的状态,甚至是等待子进程结束,可以调用wait()或者waitpid()函数让父进程等待子进程结束。

当调用exit()函数时,该进程变成僵尸进程,等待父进程回收该进程,因此调用wait()或waitpid()函数回收该进程,释放僵尸进程占用的内存空间,并且了解一下进程终止的状态信息。

wait()函数
头文件和函数原型
#include <sys/wait.h>
pid_t wait(int *wstatus);

wait()函数被调用时,系统将暂停父进程的执行,直到有信号到来或子进程结束。

如果在调用wait()函数时子进程已经结束,则会立即返回子进程结束状态值。子进程的结束状态信息会由参数wstatus返回,同时该函数会返回子进程的PID(通常是已经结束运行的子进程的PID)。如果不在意子进程的结束状态信息,则参数wstatus可以设为NULL。

wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID。

wstatus参数可使用宏定义判断子进程退出状态:

        WIFEXITED:如果子进程正常结束,返回一个非零值

        WEXITSTATUS:如果WIFEXITED非零,返回子进程退出码

        WIFSIGNALED:子进程因为捕获信号而终止,返回非零值

        WTERMSIG:如果WIFSIGNALED非零,返回信号代码

        WIFSTOPPED:如果子进程被暂停,返回一个非零值

        WSTOPSIG:如果WIFSTOPPED非零,返回信号代码

wait.c文件
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main(void)
{pid_t pid, child_pid;int status;pid = fork();if(pid < 0){printf("fork fail\n");}else if(pid == 0){printf("the child pid:%d\n", getpid());sleep(3);exit(0);}else{child_pid = wait(&status);if(child_pid == pid){printf("the father process----the child pid:%d\n", child_pid);printf("child exit status:%d\n", status);}else{printf("some error occured\n");}exit(0);}return 0;
}

Makefile文件

照旧

执行过程

waitpid()函数
头文件和函数原型
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
/* 
pid:要等待的子进程ID。< -1:等待进程组号为pid绝对值的任何子进程= -1:等待任何子进程,此时的waitpid()函数等同于wait()函数=  0:等待进程组号和目前进程相同的任何子进程,即等待任何与调用waitpid()函数且在同一个进程组的进程>  0:等待指定进程号为pid的子进程
wstatus:与wait()函数一样
options:提供了一些额外的选项来控制waitpid()函数的行为。如果不想用的话可以设为0WNOHANG:如果pid指定的子进程没有终止运行,则waitpid()函数立即返回0,而不是阻塞在这个函数上等待;如果子进程已经终止运行,则立即返回该子进程的进程号和状态信息WUNTRACED:如果子进程进入了暂停状态(可能子进程正处于被追踪等情况),则立马返回WCONTINUED:如果子进程恢复通过SIGCONT信号运行,也会立即返回(不常用)
*/

当waitpid(子进程pid, status, 0)时等同于wait(status)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/103823.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器人制作开源方案 | 杠杆式6轮爬楼机器人

1. 功能描述 本文示例将实现R281b样机杠杆式6轮爬楼机器人爬楼梯的功能&#xff08;注意&#xff1a;演示视频中为了增加轮胎的抓地力&#xff0c;在轮胎上贴了双面胶&#xff0c;请大家留意&#xff09;。 2. 结构说明 杠杆式6轮爬楼机器人是一种专门用于爬升楼梯或不平坦地面…

【elasticsearch】elasticsearch8.0.1使用rpm包安装并启用TLS

背景 公司的业务需要在加密的情况下使用&#xff0c;为此&#xff0c;研究测试了一下es8是如何启用TLS的。以下是测试使用过程。 x-pack了解 在 Elasticsearch 7.11.0 版本及更高版本中&#xff0c;X-Pack 功能在默认情况下已经整合到 Elastic Stack 的各个组件中&#xff0…

M2芯片的Mac上安装Linux虚拟机——提前帮你踩坑

M2芯片的Mac上安装Linux虚拟机——提前帮你踩坑 1. 前言1.1 系统说明1.2 Linux系统选择——提前避坑1.3 下载vmware_fusion1.3.1 官网下载1.3.2 注册 CAPTCHA验证码问题1.3.3 产品说明 1.4 下载操作系统镜像1.4.1 下载centos&#xff08;如果版本合适的&#xff09;1.4.2 下载…

Excel 自动提取某一列不重复值

IFERROR(INDEX($A$1:$A$14,MATCH(0,COUNTIF($C$1:C1,$A$1:$A$14),0)),"")注意&#xff1a;C1要空置&#xff0c;从C2输入公式 参考&#xff1a; https://blog.csdn.net/STR_Liang/article/details/105182654 https://zhuanlan.zhihu.com/p/55219017?utm_id0

c++视觉处理---直方图均衡化

直方图均衡化 直方图均衡化是一种用于增强图像对比度的图像处理技术。它通过重新分布图像的像素值&#xff0c;以使图像的直方图变得更均匀&#xff0c;从而提高图像的视觉质量。在OpenCV中&#xff0c;您可以使用 cv::equalizeHist 函数来执行直方图均衡化。以下是 cv::equal…

06-Zookeeper选举Leader源码剖析

上一篇&#xff1a;05-Zookeeper典型使用场景实战 一、为什么要看源码 提升技术功底&#xff1a;学习源码里的优秀设计思想&#xff0c;比如一些疑难问题的解决思路&#xff0c;还有一些优秀的设计模式&#xff0c;整体提升自己的技术功底深度掌握技术框架&#xff1a;源码看多…

Jenkins更换主目录

Jenkins储存所有的数据文件在这个目录下. 你可以通过以下几种方式更改&#xff1a; 使用你Web容器的管理工具设置JENKINS_HOME环境参数.在启动Web容器之前设置JENKINS_HOME环境变量.(不推荐)更改Jenkins.war(或者在展开的Web容器)内的web.xml配置文件. 这个值在Jenkins运行时…

ExcelBDD Python指南

在Python里面支持BDD Excel BDD Tool Specification By ExcelBDD Method This tool is to get BDD test data from an excel file, its requirement specification is below The Essential of this approach is obtaining multiple sets of test data, so when combined with…

【【萌新的SOC学习之自定义IP核 AXI4接口】】

萌新的SOC学习之自定义IP核 AXI4接口 自定义IP核-AXI4接口 AXI接口时序 对于一个读数据信号 AXI突发读 不要忘记 最后还有拉高RLAST 表示信号的中止 实验任务 &#xff1a; 通过自定义一个AXI4接口的IP核 &#xff0c;通过AXI_HP接口对PS端 DDR3 进行读写测试 。 S_AXI…

软件设计之抽象工厂模式

抽象工厂模式指把一个产品变成一个接口&#xff0c;它的子产品作为接口的实现&#xff0c;所以还需要一个总抽象工厂和它的分抽象工厂。 下面我们用一个案例去说明抽象工厂模式。 在class中可以选择super类和medium类&#xff0c;即选择一个产品的子类。在type中可以选择产品的…

c++处理图像---绘制物体的凸包:cv::convexHull

绘制物体的凸包&#xff1a;cv::convexHull cv::convexHull 是OpenCV中用于计算点集的凸包&#xff08;convex hull&#xff09;的函数。凸包是包围点集的最小凸多边形&#xff0c;该多边形的所有内部角都小于或等于 180 度。 cv::convexHull 函数的基本用法如下&#xff1a;…

Android Studio for Platform (ASfP) 使用教程

文章目录 编写脚本下载源代码lunch 查看版本 归纳的很清楚&#xff0c;下载Repo并下载源码->可以参考我的 Framework入门のPiex 6P源码(下载/编译/刷机) 启动图标&#xff08;重启生效&#xff09; [Desktop Entry] EncodingUTF-8 NameAndroidStudio …

大模型微调学习

用好大模型的层次&#xff1a;1. 提示词工程(prompt engineering); 2. 大模型微调(fine tuning)为什么要对大模型微调&#xff1a; 1. 大模型预训练成本非常高&#xff1b; 2. 如果prompt engineering的效果达不到要求&#xff0c;企业又有比较好的自有数据&#xff0c;能够通过…

Django实现音乐网站 ⒆

使用Python Django框架做一个音乐网站&#xff0c; 本篇主要为排行榜功能及音乐播放器部分功能实现。 目录 推荐排行榜优化 设置歌手、单曲跳转链接 排行榜列表渲染优化 视图修改如下&#xff1a; 模板修改如下&#xff1a; 单曲详情修改 排行榜列表 设置路由 视图处理…

MySQL建表操作和用户权限

1.创建数据库school&#xff0c;字符集为utf8 mysql> create database school character set utf8; 2.在school数据库中创建Student和Score表 mysql> create table school.student( -> Id int(10) primary key, -> Stu_id int(10) not null, -> C_n…

【Python 零基础入门】 Numpy

【Python 零基础入门】第六课 Numpy 概述什么是 Numpy?Numpy 与 Python 数组的区别并发 vs 并行单线程 vs 多线程GILNumpy 在数据科学中的重要性 Numpy 安装Anaconda导包 ndarraynp.array 创建数组属性np.zeros 创建np.ones 创建 数组的切片和索引基本索引切片操作数组运算 常…

C# 使用 RSA 加密算法生成证书签名产生“The system cannot find the file specified”异常

使用 C# 中 RSA&#xff08;System.Security.Cryptography.RSA&#xff09; 加密算法生成证书签名进行身份验证&#xff0c;在 VS2022 开发工具本地运行应用程序一切正常。 但将应用程序部署到远程服务器&#xff08;如&#xff1a;Azure App Services&#xff09;&#xff0c…

alsa音频pcm设备之i2c调试

i2cdetect 列举 I2C bus i2cdetect -l ls /dev/i2c* 列出I2C bus i2c-7 上面连接的所有设备,并得到i2c设备地址 i2cdetect -y 7 发现i2c设备的位置显示为UU或表示设备地址的数值,UU表示设备在driver中被使用. I2cdump i2c设备大量register的值 i2cdump -y 7 0x40 I2cset设置…

ROS中的图像数据

无论是USB摄像头还是RGBD摄像头&#xff0c;发布的图像数据格式多种多样&#xff0c;在处理这些数据之前&#xff0c;我们首先需要了解这些数据的格式。 二维图像数据 连接USB摄像头到PC端的USB接口&#xff0c;通过以下命令启动摄像头&#xff1a; roslaunch usb_cam usb_ca…

Spring Boot中的Redis自动配置与使用

Spring Boot中的Redis自动配置与使用 Redis是一种高性能的开源内存数据库&#xff0c;常用于缓存、会话管理和消息队列等场景。Spring Boot提供了自动配置来简化在Spring应用程序中使用Redis的过程。本文将介绍Spring Boot中的Redis自动配置是什么以及如何使用它来轻松集成Red…