当前位置: 首页 > java >正文

6. 进程控制

 补充一下写时拷贝中的一个细节:数据在拷贝后全都设为只读,未来要修改时,只读的数据要写入操作,碰到权限问题,此时不做异常处理,而是发生写时拷贝。

之前我们都是创建一个进程,如果我想创建多个进程呢?

也是可以的:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>#define N 10void runChild()
{int cnt = 5;while(cnt){printf("i am child:%d, ppid:%d\n", getpid(), getppid());sleep(1);cnt--;}
}int main()
{for(int i=0; i<N; i++){pid_t id = fork();if(id == 0){runChild();exit(0);}}return 0;
}

我这个进程一直创建子进程,创建出来如果是子进程就运行,如果是父进程就不做任何操作,循环回去继续创建。

1. 进程终止

我们的main函数总会有一个返回值,为什么总会return 0?1?-1?
这个值返回给谁了呢?
为什么要返回这个值?

        return 的这个值其实就是进程的退出码,表征进程的运行结果是否正确,0代表运行成功。如果我们运行这个我们写的main函数,这个值会被bash拿到。

        为什么会被bash拿到,因为进程中谁会关心我们运行进程的情况呢?一般而言就是我们运行进程的父进程会关心!父进程要把运行情况返回给用户,所以最终是用户关心进程的运行情况,也就是我们想知道运行的结果!

        若运行的结果不正确,我们想知道为什么不正确,这时就可以用return的不同返回值数字代表不同的出错原因--这也就是退出码

所以,main函数的返回值的本质:进程完成运行时是否是正确的结果,如果不是,可以用不同的数字表示不同的出错原因。

怎么拿到最近一个进程的退出码:echo $?

第二个是0是因为上一个执行的进程echo执行成功,所以它的退出码是0。

让我们通过下面的代码看一下所有错误码与其对应描述:

int main()
{for(int i=0; i<200; i++){printf("%d:%s\n", i, strerror(i));}
}
0:Success
1:Operation not permitted
2:No such file or directory
3:No such process
4:Interrupted system call
5:Input/output error
6:No such device or address
7:Argument list too long
8:Exec format error
9:Bad file descriptor
10:No child processes
11:Resource temporarily unavailable
12:Cannot allocate memory
13:Permission denied
14:Bad address
15:Block device required
16:Device or resource busy
17:File exists
18:Invalid cross-device link
19:No such device
20:Not a directory
21:Is a directory
22:Invalid argument
23:Too many open files in system
24:Too many open files
25:Inappropriate ioctl for device
26:Text file busy
27:File too large
28:No space left on device
29:Illegal seek
30:Read-only file system
31:Too many links
32:Broken pipe
33:Numerical argument out of domain
34:Numerical result out of range
35:Resource deadlock avoided
36:File name too long
37:No locks available
38:Function not implemented
39:Directory not empty
40:Too many levels of symbolic links
41:Unknown error 41
42:No message of desired type
43:Identifier removed
44:Channel number out of range
45:Level 2 not synchronized
46:Level 3 halted
47:Level 3 reset
48:Link number out of range
49:Protocol driver not attached
50:No CSI structure available
51:Level 2 halted
52:Invalid exchange
53:Invalid request descriptor
54:Exchange full
55:No anode
56:Invalid request code
57:Invalid slot
58:Unknown error 58
59:Bad font file format
60:Device not a stream
61:No data available
62:Timer expired
63:Out of streams resources
64:Machine is not on the network
65:Package not installed
66:Object is remote
67:Link has been severed
68:Advertise error
69:Srmount error
70:Communication error on send
71:Protocol error
72:Multihop attempted
73:RFS specific error
74:Bad message
75:Value too large for defined data type
76:Name not unique on network
77:File descriptor in bad state
78:Remote address changed
79:Can not access a needed shared library
80:Accessing a corrupted shared library
81:.lib section in a.out corrupted
82:Attempting to link in too many shared libraries
83:Cannot exec a shared library directly
84:Invalid or incomplete multibyte or wide character
85:Interrupted system call should be restarted
86:Streams pipe error
87:Too many users
88:Socket operation on non-socket
89:Destination address required
90:Message too long
91:Protocol wrong type for socket
92:Protocol not available
93:Protocol not supported
94:Socket type not supported
95:Operation not supported
96:Protocol family not supported
97:Address family not supported by protocol
98:Address already in use
99:Cannot assign requested address
100:Network is down
101:Network is unreachable
102:Network dropped connection on reset
103:Software caused connection abort
104:Connection reset by peer
105:No buffer space available
106:Transport endpoint is already connected
107:Transport endpoint is not connected
108:Cannot send after transport endpoint shutdown
109:Too many references: cannot splice
110:Connection timed out
111:Connection refused
112:Host is down
113:No route to host
114:Operation already in progress
115:Operation now in progress
116:Stale file handle
117:Structure needs cleaning
118:Not a XENIX named type file
119:No XENIX semaphores available
120:Is a named type file
121:Remote I/O error
122:Disk quota exceeded
123:No medium found
124:Wrong medium type
125:Operation canceled
126:Required key not available
127:Key has expired
128:Key has been revoked
129:Key was rejected by service
130:Owner died
131:State not recoverable
132:Operation not possible due to RF-kill
133:Memory page has hardware error

验证一下文件不存在但是想访问的情况:

可以看到系统提供的错误码和描述之间是一一对应的,我们可不可以自己设计一套退出码体系呢?当然可以。后面再说吧。

C语言还会引入一个全局变量errno,与退出码功能类似。

int main()
{int ret = 0;char* p = (char*)malloc(1000*1000*1000*4);if(p == NULL){printf("malloc fail, %d : %s\n", errno, strerror(errno));ret = errno;}else{printf("malloc success\n");}return ret;
}

进程退出的场景有三种:

  • 代码异常终止
  • 代码运行完毕,结果正确
  • 代码运行完毕,结果错误

一个代码结束时,肯定是先关心有无异常,然后再看运行结果是否正确。

        代码异常终止时,本质可能就是代码没有跑完,这时进程的退出码没有意义,我们不关心退出码了,但我们还得关心为什么异常,以及发生了什么异常?

进程出现异常,本质是我们的进程收到了对应的信号,什么信号呢?

就是kill中的各种信号。

一个一直在运行的进程,我们kill -几 进程pid,最后显示出来的就是以那种方式终止。

既然exit()里的值和return返回的值都可以做退出码,他们的区别是什么?

  • return 只能表示当前函数返回
  • exit 可以在任意地方被调用,都表示进程直接退出

exit 和 _exit

int main()
{printf("what can i say");sleep(1);exit(11);
}

1s后打印what can i say

int main()
{printf("what can i say");sleep(1);_exit(11);
}

只是把exit替换成了_exit,就不打印了?

_exit 退出时,没有打印缓冲区中的数据。所以他们的区别:

内核上面的是用户区,exit()是库函数,_exit()是系统调用_exit直接退出,什么都不干,但是exit会刷新缓冲区等等,搞完之后才会调用_exit退出。

所以我们 printf 写入的缓冲区绝对不在内核中

在main中,return n 等价于 exit(n),因为main运行时会将main的返回值当作exit 的参数。

2. 进程等待

之前说过,子进程退出,如果父进程还在运行而且对子进程不管不顾,不处理子进程返回的信息,那么就会形成僵尸进程,造成内存泄漏。

父进程怎么回收这个Z状态的子进程呢?通过进程等待,回收子进程资源,获取子进程退出信息。

进程等待是什么:

        通过系统调用接口 wait/waitpid,来对子进程进行状态检查和回收的功能。

为什么要有进程等待?

        僵尸进程无法被杀死,需要通过进程等待来处理掉他,进而解决内存泄漏问题--必须解决的
        我们需要通过进程等待,获取子进程的退出情况,父进程需要知道布置给子进程的任务完成的怎么样--要么关心,要么不关心,这是可选的。

wait:

int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){int cnt = 5;while(cnt){printf("i am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt);cnt--;sleep(1);}exit(0);}else{int cnt = 10;while(cnt){printf("i am father, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt);cnt--;sleep(1);}pid_t ret = wait(NULL);if(ret == id){printf("wait success, ret:%d\n", ret);}sleep(5);}return 0;
}

        子进程9669结束后,父进程还存在,然后wait,wait是等待任意一个子进程退出,返回它等待到的退出的这个子进程的pid,如果wait的返回值和传给父进程的id(子进程pid)一样,说明等待成功。

学到目前为止,进程等待是必须的!

上面是单个进程的情况,如果有多个进程呢?父进程创建了那么多子进程,都得回收,怎么回收呢

#define N 10void runChild()
{int cnt = 5;while(cnt){printf("i am child:%d, ppid:%d\n", getpid(), getppid());sleep(1);cnt--;}
}int main()
{for(int i=0; i<N; i++){pid_t id = fork();if(id == 0){runChild();exit(0);}printf("create child process:%d success\n", id);}sleep(5);for(int i=0; i<N; i++){pid_t id = wait(NULL);if(id > 0){printf("wait %d success\n", id);}}return 0;
}

其实直接搞一个循环,让他等待任意一个进程退出就好。

如果没有一个子进程退出呢?也就是父进程在wait时,子进程都不退,则会在wait处阻塞等待。

​#define N 10void runChild()
{int cnt = 5;while(1){printf("i am child:%d, ppid:%d\n", getpid(), getppid());sleep(1);cnt--;}
}int main()
{for(int i=0; i<N; i++){pid_t id = fork();if(id == 0){runChild();exit(0);}printf("create child process:%d success\n", id);}sleep(5);for(int i=0; i<N; i++){pid_t id = wait(NULL);if(id > 0){printf("wait %d success\n", id);}}return 0;
}

waitpid:

pid:

  • pid = -1:等待任意一个子进程,与wait等效
  • pid > 0:等待进程id和pid相等的子进程(某一子进程)

stat_loc:

  • NULL:不关心子进程退出状态信息

options:

  • 0:阻塞等待
  • WNOHANG:非阻塞轮询

waitpid(-1, NULL,0)和wait(NULL)是一样的效果。

pid很好理解,我们详细介绍其他两个参数

stat_loc:

stat_loc,共有32位,我们只考虑其中的16位。

前面提到了,子进程退出一共就3种退出场景,父进程等待,期望获得子进程退出时的哪些信息呢?

首先是子进程代码是否异常,一旦代码异常,就会被kill的信号所杀,这个信号就是kill -l中的各种信号:

“第几个信号”导致进程异常退出,这个退出信息存在32位的哪里呢?

可以看到低7位存放的就是异常信号信息。由于kill中没有0号信号,要检测当前进程是否异常,只用检测低7位是否为0就可以,若为0,说明进程没有收到kill信号,没有异常。

判断完有无异常,就要判断程序运行的是否正确了,这我们在进程终止中讲过,是通过退出码与其对应信息拿到的,8-15位就存的是退出码。这个core dump暂时不讲。

    pid_t ret = waitpid(id, &status, 0);if(ret == id){//7F = 0111 111printf("wait success, ret:%d, exit sig:%d, exit code:%d\n", ret, status&0x7F, (status>>8)&0xF);

这样就可以看到退出码和终止信号。

父进程通过waitpid读取子进程的PCB,检查子进程若为僵尸状态,则把这两个exit信息合并位status,让上层拿到,在底层的PCB代码中也是有这两个值的。exit_signal和exit_code。

子进程执行完毕后,代码和数据可以释放,但是PCB不能,因为其中包含退出信息,需要等父进程来接收。

waitpid成功了就返回接收的那个子进程的pid,失败了就返回-1,怎么才能接受失败呢?

搞一个限定接收某一个进程,但是给父进程传的id根本不是这个进程的,接收不到就失败了。

但是吧,exit的信息如果这么用位运算,代码不易读懂,所以系统提供了两个宏

  • WIFEXITED:若为正常终止子进程返回的状态,则为真(检查进程是否正常退出)
  • WEXITSTATUS:若WIFEXITED非零,提取子进程退出码(查看进程的退出码)
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){int cnt = 5;while(cnt){printf("i am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt);cnt--;sleep(1);}exit(0);}else{int cnt = 10;while(cnt){printf("i am father, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt);cnt--;sleep(1);}pid_t ret = wait(NULL);int status;if(ret == id){if(WIFEXITED(status)){printf("进程是正常跑完的,退出码:%d\n", WEXITSTATUS(status));}else{printf("进程出异常了\n");}printf("wait success, ret:%d\n", ret);}else{printf("wait failed\n");}}return 0;
}

options:

        阻塞等待的意思就是,如果子进程状态不满足,父进程就一直等待,直到目标子进程状态改变(退出、暂停或被信号终止),父进程需要等待子进程完成后再继续执行

        非阻塞轮询就是使用 WNOHANG 选项waitpid(pid, &status, WNOHANG)立即返回

  • 如果目标子进程已退出/终止,则返回其 PID,并填充 status

  • 如果子进程仍在运行,则返回 0(不阻塞)。

#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>int main() 
{pid_t pid = fork();if (pid == 0) {// 子进程sleep(2);  // 模拟子进程运行printf("Child process exiting.\n");_exit(42);} else {// 父进程int status;while (1) {pid_t ret = waitpid(pid, &status, WNOHANG);  // 非阻塞轮询if (ret == pid) {printf("Child exited with code: %d\n", WEXITSTATUS(status));  // 输出 42break;} else if (ret == 0) {printf("Child still running...\n");sleep(1);  // 避免 CPU 忙等待} else {perror("waitpid error");break;}}}return 0;
}

父进程不会阻塞,而是每隔 1 秒检查一次子进程状态。 

 ret = 0就说明等待的条件没有就绪,非阻塞轮询的意义就是可以去做别的事情然后继续等待。

http://www.xdnf.cn/news/4830.html

相关文章:

  • 基于51单片机的自动洗衣机衣料材质proteus仿真
  • 冯诺依曼体系结构与操作系统
  • 2.6 点云数据存储格式——小结
  • 1128. 等价多米诺骨牌对的数量
  • Python Cookbook-7.7 通过 shelve 修改对象
  • HPLC+HRF双模载波组网过程简析
  • 【嵌入式开发-SDIO】
  • 前端获取流式数据并输出
  • 【Day 22】HarmonyOS车联网开发实战
  • vfrom表单设计器使用事件机制控制字段显示隐藏
  • 算法解密:除自身以外数组的乘积问题详解
  • robot_lab中amp_utils——retarget_kp_motions.py解析
  • 算法训练营第十一天|150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素
  • 旅游设备生产企业的痛点 质检系统在旅游设备生产企业的应用
  • Python pandas 向excel追加数据,不覆盖之前的数据
  • <C#>log4net 的配置文件配置项详细介绍
  • python24-匿名函数
  • 2.5 特征值与特征向量
  • 二叉树的基本操作
  • es6/7练习题1
  • 微软推动智能体协同运作:支持 A2A、MCP 协议
  • mqtt选型,使用
  • 关键字where
  • Docker学习笔记
  • deeplabv3+街景图片语义分割,无需训练模型,看不懂也没有影响,直接使用,cityscapes数据集_25
  • python小说网站管理系统-小说阅读系统
  • 什么是HTML、CSS 和 JavaScript?
  • 电容的基本介绍
  • WPF 子界面修改后通知到主页面
  • 嵌入式 C 语言控制语句