UC成长之路9
作者:互联网
回顾:
UC成长之路1
UC成长之路2
UC成长之路3
UC成长之路4
UC成长之路5
UC成长之路6
UC成长之路7
UC成长之路8
一、遗言函数的注册
- atexit(3)回顾UC成长之路8
- on_exit(3)
#include <stdlib.h>
int on_exit(void (*function)(int , void *), void *arg);
//功能:给进程注册遗言函数
//参数
//function:遗言函数的入口地址
//arg:传递给function函数的第二个参数
//返回值:成功返回0,失败返回非0
eg:
#include <stdio.h>
#include <stdlib.h>
//遺言函數
void doit(int status, void* arg)
{
printf("n=%d\targ=%s\n", status, (char *)arg);
return;
}
int main(void)
{
//向進程注冊遺言函數
on_exit(doit, "hello");
getchar();
return 0;
}
注意: 遗言函数的三个注意点:
- 1、调用的顺序和注册的顺序相反;
- 2、同一个函数被注册一次就被调用一次
- 3、子进程继承父进程的遗言函数
#include <stdio.h>
#include <stdlib.h>
//遺言函數
void doit(int status, void* arg)
{
printf("status=%d\targ=%s\n", status, (char *)arg);
return;
}
void dou(int status, void* arg)
{
printf("stutus%d\targ=%s\n", status, (char *)arg);
return;
}
int main(void)
{
//向進程注冊遺言函數
on_exit(doit, "hello");
on_exit(doit, "hello");
on_exit(dou, "world");
getchar();
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
//遺言函數
void doit(int status, void* arg)
{
printf("status=%d\targ=%s\n", status, (char *)arg);
printf("pid=%d\n", getpid());
return;
}
void dou(int status, void* arg)
{
printf("stutus%d\targ=%s\n", status, (char *)arg);
printf("pid=%d\n", getpid());
return;
}
int main(void)
{
//向進程注冊遺言函數
on_exit(doit, "hello");
on_exit(dou, "world");
//on_exit(doit, "hello");
pid_t pfork = fork();
if(pfork==-1){
perror("fork");
return -1;
}
if(pfork==0){
exit(0);
}
else{
}
getchar();
return 0;
}
二、进程资源的回收
- 进程在执行期间占用一定的资源,在进程终止的时候需要将资源释放给系统,由父进程负责回收子进程的资源。
- 系统提供了wait(2)家族函数用于回收进程的资源
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
//功能:等待子进程的结束,子进程结束以后,回收子进程的资源
//参数
//status:
//1.WIFEXITED(status):检测子进程是否正常终止,正常终止返回真。
//2.WEXITSTATUS(status):在子进程正常终止时使用,即WIFEXITED(status)返回真时使用,检测子进程的退出状态。
//3.WIFSIGNALED(status):如果子进程被信号终止,返回真
//4.WTERMSIG(status):只有在WIFSIGNALED(status)为真时使用,返回终止子进程的信号的编号
//返回值:成功返回终止子进程的pid;错误返回-1
pid_t waitpid(pid_t pid, int *status, int options);
//功能:等待指定子进程的结束,子进程结束以后,回收子进程的资源
//参数
//pid:可以指定具体一个pid
//pid的取值:1)<-1:pid的绝对值是要等待的子进程的组id
//2)-1:等待任意子进程
//3)0:等待子进程,这些进程被等待的子进程的组id和父进程的组id必须一致
//4)>0:等待指定的进程id
//status:和wait()的status一样
//options:1.WNOHANG:如果没有子进程终止,立即返回;2.0为阻塞
//返回值:成功:返回被终止的子进程的pid,如果WNOHANG被指定,没有子进程终止,返回0;错误返回-1
waitpid(-1, &status, 0);
- 进程组:进程组中有多个进程
- 僵尸进程:子进程退出后系统资源没有被父进程回收就是僵尸进程
eg: zomdie.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main(void)
{
//創建子進程
pid_t pid=fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
printf("child processing...%d\n", getpid());
exit(0);//馬上退出
}
else{//父進程執行的代碼
getchar();
}
return 0;
}
- 孤儿进程:父进程终止了,子进程的父进程改为init进程
eg: orphan.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main(void)
{
//創建子進程
pid_t pid=fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
printf("child processing...%d\n", getppid());
sleep(2);//確保父進程已經終止
printf("child processing...%d\n", getppid());
}
else{//父進程執行的代碼
sleep(1);//父進程睡眠1s
}
return 0;
}
eg: wait.c
使用wait(2)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
//創建子進程
pid_t pid=fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
printf("child processing...%d\n", getpid());
getchar();
exit(0);
}
else{//父進程執行的代碼
wait(NULL);
printf("parent processing...%d\n", getpid());
}
return 0;
}
eg:waitt.c
检测子进程终止原因
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
int status;
//創建子進程
pid_t pid=fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
printf("child processing...%d\n", getpid());
//getchar();
exit(0);
}
else{//父進程執行的代碼
wait(&status);//等待子進程終止,將子進程的推出狀態存儲到s變量空間裏
printf("parent processing...%d\n", getpid());
//檢測子進程是否正常終止
if(WIFEXITED(status))
printf("exit code %d\n", WEXITSTATUS(status));
}
return 0;
}
eg:进程被信号终止
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
int status;
//創建子進程
pid_t pid=fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
printf("child processing...%d\n", getpid());
getchar();
exit(0);
}
else{//父進程執行的代碼
wait(&status);//等待子進程終止,將子進程的推出狀態存儲到s變量空間裏
printf("parent processing...%d\n", getpid());
//檢測子進程是否正常終止
if(WIFEXITED(status))
printf("exit code %d\n", WEXITSTATUS(status));
//檢測子進程是否被信號終止
if(WIFSIGNALED(status))
printf("signal num %d\n", WTERMSIG(status));
}
return 0;
}
补充:如何给指定的进程发送信号?
kill -信号编号 pid
eg: waitpid.c
举例说明waitpid(2)
- 验证阻塞
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
//int status;
//創建子進程
pid_t pid=fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
printf("child processing...%d\n", getpid());
getchar();
exit(0);
}
else{//父進程執行的代碼
waitpid(-1, NULL, 0);//等價與wait(NULL);
printf("parent processing...%d\n", getpid());
}
return 0;
}
- 验证非阻塞
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
//int status;
//創建子進程
pid_t pid=fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
printf("child processing...%d\n", getpid());
getchar();
exit(0);
}
else{//父進程執行的代碼
int w=waitpid(-1, NULL, WNOHANG);//非阻塞,沒有子進程終止的時候,返回0
printf("parent processing...%d\n", getpid());
printf("w=%d\n",w);
}
return 0;
}
三、更换进程的映像
- fork复制父进程的PCB,进程有自己的4G空间,但是这时候子进程和父进程执行的程序代码还完全一样。子进程重复执行父进程,没有发展。
- 需要子进程发展创新,进程的映像属于PCB的一个部分。在子进程的空间里装入指定程序映像,将原来从父进程继承的映像替换。子进程就有了自己的空间,有了自己的执行程序的映像。
- 系统提供一下函数完成了更新进程的映像:execve(2)
#include <unistd.h>
int execve(const char *filename, char *const argv[],
char *const envp[]);
//功能:执行程序
//参数
//filename:指定了要执行的程序的文件名
//argv:字符串列表,按照惯例,第一个字符串是filename
//envp:通过这个参数给进程传递环境变量。形式是key=value
//返回值:成功不返回;失败返回-1,errno被设置
- 除了execve(2)函数外,系统封装这个函数提供了很多的库函数用于替换进程的映像。这些函数都属于execl(3)家族
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
- 上面的函数都是exec与l、v、p、e组合而成:
- l:argv[0], argv[1],…, NULL。就是要将argv[]数组每个都要列出来
- v:vector argv。只需要给出argv[]数组的地址就行,与字母l相对
- p:指环境变量PATH。如果函数的名字里有这个字母,只需要指定可执行文件的名字即可,可执行程序到PATH环境变量指定的路径下找到这个文件。如果函数的名字没有这个字母,需要指定可执行文件的路径。
- e:环境变量。函数的名字中没有这个字符,从父进程默认继承环境变量。如果有这个字符,使用指定的环境变量覆盖从父进程继承而来的环境变量。
执行命令:ps -o pid,ppid,pgrp,session,comm
day09$ps -o pid,ppid,pgrp,session,comm
PID PPID PGRP SESS COMMAND
2429 2424 2429 2429 bash
2914 2429 2914 2429 ps
eg:使用exec家族的函数执行上面的命令ps -o pid,ppid,pgrp,session,comm
execvp.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
char *const p_argv[]={"ps", "-o", "pid,ppid,pgrp,session,comm",NULL};
//創建子進程
pid_t pid = fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
//使用ps命令的映像替換父進程繼承來的進程映像
execvp("ps", p_argv);
//以下代碼只有在上面的execp()函數執行出錯才會執行
perror("execl");
exit(1);
}
else{//父進程執行的代碼
wait(NULL);//阻塞等待子進程的結束
}
return 0;
}
execv.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
char *const p_argv[]={"ps", "-o", "pid,ppid,pgrp,session,comm",NULL};
//創建子進程
pid_t pid = fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
//使用ps命令的映像替換父進程繼承來的進程映像
//execvp("ps", p_argv);
execv("/bin/ps", p_argv);
//以下代碼只有在上面的execp()函數執行出錯才會執行
perror("execl");
exit(1);
}
else{//父進程執行的代碼
wait(NULL);//阻塞等待子進程的結束
}
return 0;
}
execlp.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
//char *const p_argv[]={"ps", "-o", "pid,ppid,pgrp,session,comm",NULL};
//創建子進程
pid_t pid = fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
//使用ps命令的映像替換父進程繼承來的進程映像
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,comm",NULL);
//以下代碼只有在上面的execp()函數執行出錯才會執行
perror("execl");
exit(1);
}
else{//父進程執行的代碼
wait(NULL);//阻塞等待子進程的結束
}
return 0;
}
execl.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
//char *const p_argv[]={"ps", "-o", "pid,ppid,pgrp,session,comm",NULL};
//創建子進程
pid_t pid = fork();
if(pid==-1){
perror("fork");
return -1;
}
if(pid==0){//子進程執行的代碼
//使用ps命令的映像替換父進程繼承來的進程映像
execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,comm",NULL);
//以下代碼只有在上面的execp()函數執行出錯才會執行
perror("execl");
exit(1);
}
else{//父進程執行的代碼
wait(NULL);//阻塞等待子進程的結束
}
return 0;
}
- exec家族的函数是用来替换进程的映像。
执行a.out的时候发生了什么?bash首先通过fork(2)创建子进程,然后execl(2)更换进程的映像为a.out。
eg:验证exec函数就是替换进程的映像
#include <unistd.h>
int main(void)
{
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,comm", NULL);
return 0;
}
- bash上执行的是linux命令,这些命令分为内部命令和外部命令
- 外部命令都是独立的可执行程序,bash也是一个独立的可执行程序。和bash不是同一个命令,这些命令都是外部命令。
- 内部命令就是bash的一部分程序而已。bash执行这些程序的时候,不用fork。
- 那么怎么查看一个命令是内部命令还是外部命令?使用命令
type command
,例如type ls
标签:status,int,之路,pid,成长,进程,include,UC,進程 来源: https://blog.csdn.net/weixin_42284219/article/details/115757752