> 技术文档 > 【Linux系统】详解,进程控制

【Linux系统】详解,进程控制


前言:

        上文我们讲到了Linux中的虚拟空间地址,知道了一个进程对应一个虚拟地址空间,虚拟空间地址与物理地址之间通过页表映射....【Linux】虚拟地址空间-CSDN博客

        本文我们来讲一讲Linux系统是如何控制进程的!

        如果喜欢本期文章,请点点关注吧!非常感谢佬的支持   _(:з」∠)_                                 


进程创建

fork函数

        fork函数是Linux系统提供的接口,其功能就是创建子进程。

        既调用fork函数,系统就自动为我们创建好了子进程

#includepid_t fork();其中pid_t是Linux中的数据类型,相当于int,即为整型

        fork的返回值有两个,对于父进程:返回子进程的pid,对于子进程:返回0

#include #include  int main(){ pid_t pid = fork(); if(pid 0) { printf(\"我是一个父进程:%d,这是我的父进程:%d\\n\",getpid(),getppid()); } } 

        对于fork原理的详细介绍可参考【Linux】初见,进程概念-CSDN博客中的第四节“如何创建进程”,这里面有超级详细的介绍!

fork的用法

        一个父进程希望复制自己,使父子进程同时执行不同的代码段。e.g. 父进程等待客户端响应,生成子进程处理请求。

        一个进程想要执行多个不同的代码。e.g. 生成子进程调用exec函数。

fork失败原因

        系统中的进程太多了

        用户的进程数量超过了限制


进程终止

进程终止的本质就是进程结束,系统释放资源:释放进程申请的相关数据结构和对应的代码与数据

进程退出的场景

        1.代码运行完毕,结果正确(退出码为0)

        2.代码运行完毕,结果不正确(退出码为非0)

        3.代码异常终止(进程接收到型号终止,退出码无意义)

进程退出方法

正常退出:

        1.从main函数返回

        2.调用exit

        3._exit

异常退出:

        信号终止

退出码

        退出码可以告诉我们进程终止时的状态,0代表执行成功,非0则代表不成功

        非0的退出码中,一个值对应一个错误原因。可以使用strerror函数获取退出码对应的信息。

#include #includeint main(){ for(int i=0;i %s\\n\",i,strerror(i));  } }
0 -> Success1 -> Operation not permitted2 -> No such file or directory3 -> No such process4 -> Interrupted system call5 -> Input/output error6 -> No such device or address7 -> Argument list too long8 -> Exec format error9 -> Bad file descriptor10 -> No child processes11 -> Resource temporarily unavailable12 -> Cannot allocate memory13 -> Permission denied14 -> Bad address15 -> Block device required16 -> Device or resource busy17 -> File exists18 -> Invalid cross-device link19 -> No such device20 -> Not a directory21 -> Is a directory22 -> Invalid argument23 -> Too many open files in system24 -> Too many open files25 -> Inappropriate ioctl for device26 -> Text file busy27 -> File too large28 -> No space left on device29 -> Illegal seek30 -> Read-only file system31 -> Too many links32 -> Broken pipe33 -> Numerical argument out of domain34 -> Numerical result out of range35 -> Resource deadlock avoided36 -> File name too long37 -> No locks available38 -> Function not implemented39 -> Directory not empty40 -> Too many levels of symbolic links41 -> Unknown error 4142 -> No message of desired type43 -> Identifier removed44 -> Channel number out of range45 -> Level 2 not synchronized46 -> Level 3 halted47 -> Level 3 reset48 -> Link number out of range49 -> Protocol driver not attached50 -> No CSI structure available51 -> Level 2 halted52 -> Invalid exchange53 -> Invalid request descriptor54 -> Exchange full55 -> No anode56 -> Invalid request code57 -> Invalid slot58 -> Unknown error 5859 -> Bad font file format60 -> Device not a stream61 -> No data available62 -> Timer expired63 -> Out of streams resources64 -> Machine is not on the network65 -> Package not installed66 -> Object is remote67 -> Link has been severed68 -> Advertise error69 -> Srmount error70 -> Communication error on send71 -> Protocol error72 -> Multihop attempted73 -> RFS specific error74 -> Bad message75 -> Value too large for defined data type76 -> Name not unique on network77 -> File descriptor in bad state78 -> Remote address changed79 -> Can not access a needed shared library80 -> Accessing a corrupted shared library81 -> .lib section in a.out corrupted82 -> Attempting to link in too many shared libraries83 -> Cannot exec a shared library directly84 -> Invalid or incomplete multibyte or wide character85 -> Interrupted system call should be restarted86 -> Streams pipe error87 -> Too many users88 -> Socket operation on non-socket89 -> Destination address required90 -> Message too long91 -> Protocol wrong type for socket92 -> Protocol not available93 -> Protocol not supported94 -> Socket type not supported95 -> Operation not supported96 -> Protocol family not supported97 -> Address family not supported by protocol98 -> Address already in use99 -> Cannot assign requested address100 -> Network is down101 -> Network is unreachable102 -> Network dropped connection on reset103 -> Software caused connection abort104 -> Connection reset by peer105 -> No buffer space available106 -> Transport endpoint is already connected107 -> Transport endpoint is not connected108 -> Cannot send after transport endpoint shutdown109 -> Too many references: cannot splice110 -> Connection timed out111 -> Connection refused112 -> Host is down113 -> No route to host114 -> Operation already in progress115 -> Operation now in progress116 -> Stale file handle117 -> Structure needs cleaning118 -> Not a XENIX named type file119 -> No XENIX semaphores available120 -> Is a named type file121 -> Remote I/O error122 -> Disk quota exceeded123 -> No medium found124 -> Wrong medium type125 -> Operation canceled126 -> Required key not available127 -> Key has expired128 -> Key has been revoked129 -> Key was rejected by service130 -> Owner died131 -> State not recoverable132 -> Operation not possible due to RF-kill133 -> Memory page has hardware error

        使用echo $?,可以打印出最近一个程序的退出码。

        退出码是进程的性质之一,所以退出码会保存到进程的PCB中。

_exit函数

#includevoid _exit(int status);在任何地方调用_exit函数,都会让当前进程结束并以给定的值作为退出码退出

exit函数

#includevoid exit(int status);与_exit函数功能类似都是以指定的退出码,退出当前进程

区别

        _exit函数系统调用,而exit是C语言提供。

        使用exit函数退出时,会进行缓冲区的刷新。反之_exit则不会。

举个例子说明:

        程序结束刷新缓冲区,信息打印在屏幕上,反之没有信息打印

        感兴趣的朋友可以看看这篇文章,这里有详细的缓冲区刷新介绍【Linux】LInux下第一个程序:进度条-CSDN博客

#include #include#includeint main(){ printf(\"yuzuriha\"); sleep(1); exit(0); }

#include #include#includeint main(){ printf(\"yuzuriha\"); sleep(1); _exit(0);}


进程等待

进程等待的必要性

        1.之前我们讲过子进程的退出后,子进程回进入僵尸状态,必须要被父进程回收才行。若子进程一直没有被父进程回收,就会一直处于僵尸状态,进而造成内存泄漏。

        2.进程被创造出来执行任务,结果如何,是成功、失败还是异常,这是父进程要得知的必要信息。

        所以回收子进程是必要的,父进程通过进程等待的方式,回收子进程进程资源、获取子进程的退出信息。

进程等待的方法

wait方法
#include#includepid_t wait(int* status);返回值:等待成功返回对应的子进程pid,失败则返回-1参数status:为输出型参数,可以获取子进程的退出码 若不需要,可以传NULL忽略这个参数
waitpid函数
#include#includepid_ t waitpid(pid_t pid, int *status, int options);

下面详细介绍一下每一个参数:

pid_t pid

参数pid:-1,表示可以等待任意一个子进程。

参数pid:>0,表示只能等待当前这个进程的子进程。

status

        如上面所讲,status是一个输出型参数,由操作系统填充,我们可以通过status获取进程状态

        status并不是一个简单的整型,细节如下:

        1.status是一个低16位有效的int,通过位划分存储子进程的信息

        2.正常终止的情况下:低7位全为0,15~8存放退出码

                获取退出码:WEXITSTATUS(status)

        3.非正常退出:退出码无意义,第7位存放标志,6~0存放信号编号

                判断一个进程是否位正常退出:WIFEXITED(status),正常为真。

options

        options的默认值是0,表示阻塞等待

        options为:WNOHANG时表示非阻塞等待

        简而言之,阻塞等待就是等待子进程结束的过程中,父进程不能运行。而非阻塞等待,就是等待过程中父进程可以执行自己的代码。

        当参数为:WNOHANG时,waitpid会进行非阻塞轮询等待结束,返回>0(pid)本次调用结束,但子进程还没有退出,返回0调用失败,返回<0;


进程程序替换 

        程序替换就是字面上的意思,替换掉原有的程序,先来直接看看效果

#include   #includeint main(){ printf(\"开始\\n\"); execl(\"/usr/bin/ls\",\"ls\",\"-l\",NULL); printf(\"结束\\n\");}

         我们可以看到执行就第一个printf打印出了结果,第二行就执行就程序替换,将原来的程序替换为了ls指令。

替换原理

        exec系列函数,其功能就是用新的程序替换原本的程序。

        如图,替换程序的本质是将物理内存中的数据,用新数据覆盖,页表中的物理地址重新覆盖填写,其他的不动。

注意:

        1.一旦程序替换成功,就会去执行新代码了,旧代码以及不复存在了。

        2.exec系列函数只有在失败的时候才有返回值,既只要返回就是失败。

        3.程序替换并没有创建新进程,只是进行了数据的覆盖。

认识全部exec函数

execl
int execl(const char* path , const char* arg , ...)l:代表list,以链表的形式传参path:路径+程序名,表示要执行谁arg:平时怎么写指令,这里就怎么写。表示如何执行注:一定要以NULL结尾!!!
#include   #includeint main(){ printf(\"开始\\n\"); execl(\"/usr/bin/ls\",\"ls\",\"-l\",NULL); printf(\"结束\\n\");}

不仅可以替换系统程序,也可以替换我们自己写的程序:

#include #includeint main(){ printf(\"开始\\n\"); execl(\"./x\",\"./x\",NULL);  printf(\"结束\\n\");}

        我们知道数据和代码父子进程默认是共享的,那程序替换对父进程有影响吗?

        没有,因为数据和代码都会进行写时拷贝

execlp
int execlp(const char* file , const char* arg , ...)l:表示list,以链表的形式传参p:表示环境变量PATHfile:有了PATH环境变量,我们就不用写路径了。 直接写我们要执行的程序名即可arg:同上,与我们平时写的指令无异 但必须以NULL结尾
#include #includeint main(){ printf(\"开始\\n\"); execlp(\"ls\",\"ls\",\"-l\",NULL); printf(\"结束\\n\");}

execv
int execv(const char* path , char* const argv[])v:表示vector,用数组的方式传参 数组中,依旧想要以NULL结尾
#include #includeint main(){ char* const argv[]={ (char* const)\"ls\", (char* const)\"-l\", NULL }; printf(\"开始\\n\"); execv(\"/usr/bin/ls\",argv); printf(\"结束\\n\");}

 execvp
int execvp(const char* file , char* const argv[])v:vector,以数组形式传参p:环境变量PATH

同上,就不举例了

execvpe
int execvpe(const char* flie , char* const argv[] , char* cosnt envp[])相比上面,多了一个ee:环境变量!传入envp数组,那被替换的进程的环境变量会被evnp[]直接覆盖这会导致我们无法使用原来的环境变量

        所以我们一般采用新增环境变量的方式,而不是直接覆盖。

        法一:调用系统调用:putevn(char* string)新增环境变量,再调用其他exec函数实现

        法二:调用系统调用:putevn(char* string)新增环境变量,调用execvpe时,传入环境变量指针:environ

execle
int execle(const char* path , char* const argv .... , char* cosnt envp[])同上
execve
int execve(const char* path , char* const argv[] ... , char* cosnt envp[])同上

值得一提的是execve的系统调用,而其他函数都是C语言提供的

股市资讯