除了之前提到的fork(),还有两种进程原语-execl()和wait()。那就让我们再来回顾一下这两种吧。
execl():
在一个进程中使用execl(),将会改变当前进程的功能。原有的内核层信息不变,但用户层的数据将会被重载的内容覆盖,也就是当execl()返回后,原来的剩余任务将不会再执行,取而代之的是重载后的新任务。当使用fork()产生子进程时,就可以在子进程中进行重载使子进程能执行别的功能而不是继承自父进程的工作。
下面举一个execl()的例子:
int main(){
printf("Before executing\n");
if (execl("/home/changhao/4_linux/program/app", "app", NULL) == -1) {
perror("execl failed");
}
printf("After executing\n");
return 0;
}
我将这个程序重载成了另一个程序app,可以看到app的输出如下(输出的是进程id和_):
当我重载后,可以看到重载前的内容正常输出,重载后的内容输出后就没有再输出原来没输出的内容,这是因为这个进程的用户层数据已经被覆盖了,原来的内容已经不见了。
下面的输出是不进行重载的情况。
注意的一点:execl的参数NULL是需要写的,不然无法重载。
wait():
wait()主要是用来实现僵尸进程的回收,那什么是僵尸进程呢,僵尸进程就是当进程结束后但他的一些数据(PCB)没有被回收,产生了内存泄漏。当exit()函数执行的时候,底层调用了_EXIT函数,将进程用户层的数据回收,并且将内核层除了PCB的数据都回收,这样就导致了PCB的内容没有被回收,进而产生僵尸进程。
而wait()就是用来回收PCB来处理僵尸进程的。
参数status用来返回子进程退出的信息,以此得知怎么退出。如果不想得知原因可以传NULL。
参数一般以宏的形式包含在
WIFEXITED(status)
: 如果子进程正常结束,则该宏返回非零值。在这种情况下,可以使用 WEXITSTATUS(status)
宏来获取子进程的退出码。
WEXITSTATUS(status)
: 如果 WIFEXITED
非零,它返回子进程的退出状态。这通常是 main
函数的返回值或 exit()
调用的参数。
WIFSIGNALED(status)
: 如果子进程是因为未捕获的信号而结束的,则该宏返回非零值。在这种情况下,可以使用 WTERMSIG(status)
宏来获取导致子进程终止的信号编号。
WTERMSIG(status)
: 如果 WIFSIGNALED
非零,它返回一个信号编号,这个编号是导致子进程终止的信号。
WIFSTOPPED(status)
: 如果子进程是因为信号而停止的,则该宏返回非零值。这仅适用于使用 waitpid()
时,并且指定了 WUNTRACED
或 WCONTINUED
标志。
WSTOPSIG(status)
: 如果 WIFSTOPPED
非零,它返回导致子进程停止的信号编号。WIFCONTINUED(status)
: 如果子进程已经恢复执行,此前它是因为信号而停止的,则该宏返回非零值。这仅适用于使用 waitpid()
时,并且指定了 WCONTINUED
标志。
注意wait函数是父进程用来回收子进程。如果父进程没有子进程调用会失败。而且如果有多个子进程依次只能回收一个。需要多次调用。在回收子进程结束前父进程需要阻塞。
waitpid()可以解决父进程阻塞的问题,无需使父进程持续陷入阻塞,可以让回收与任务之间切换,交替执行。
pid_t waitpid(pid_t pid, int *status, int options);
相比于wait()多了两个参数。
对于Pid:
-1回收任意子进程,
>0,传递一个指定进程的id,回收指定进程
0,表示回收同组的任意子进程(同组回收策略)
<-1,指定组id,实现跨组回收(组号为参数的绝对值)
对于options
WNOHANG
:使 waitpid()
函数变为非阻塞模式,即如果没有子进程退出,waitpid()
会立即返回0,而不是阻塞等待。
WUNTRACED
:除了返回终止的子进程信息外,还返回因信号而停止的子进程信息。
WCONTINUED
:返回那些已经被停止,然后又继续执行的子进程状态信息。
返回值:
如果成功,则返回收集到状态信息的子进程的PID。
如果设置了 WNOHANG
,但没有子进程退出,则返回0。
如果出错,则返回-1。
#include
#include
#include
int main() {
pid_t pid = fork();
if (pid == -1) {
// 错误处理
perror("fork");
return 1;
} else if (pid > 0) {
// 父进程
int status;
pid_t wpid = waitpid(pid, &status, WUNTRACED | WCONTINUED);
if (wpid == -1) {
perror("waitpid");
return 1;
}
if (WIFEXITED(status)) {
printf("Child exited with status %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child killed by signal %d\n", WTERMSIG(status));
}
} else {
// 子进程
// 执行一些操作,然后退出
printf("Child process\n");
_exit(0); // 使用 _exit() 来确保子进程正确退出
}
return 0;
}
根据输出,判断出了子进程正常结束。
下面再举一个回收的例子:
#include
#include
#include
#include
int main() {
int n = 5; // 假设创建5个子进程
pid_t pids[5];
for (int i = 0; i < n; i++) {
pids[i] = fork();
if (pids[i] < 0) {
perror("fork");
exit(1);
} else if (pids[i] == 0) {
// 子进程
printf("Child %d\n", i);
sleep(1); // 假设子进程执行任务
_exit(0);
}
}
while(1) sleep(1);
return 0;
}
没有回收导致都是子进程都变成了僵尸进程。
添加回收:
#include
#include
#include
#include
int main() {
int n = 5; // 假设创建5个子进程
pid_t pids[5];
for (int i = 0; i < n; i++) {
pids[i] = fork();
if (pids[i] < 0) {
perror("fork");
exit(1);
} else if (pids[i] == 0) {
// 子进程
printf("Child %d\n", i);
sleep(1); // 假设子进程执行任务
_exit(0); // 子进程退出
}
}
// 父进程:回收所有子进程
int status;
pid_t pid;
while (n > 0) {
pid = waitpid(-1, &status, 0); // 等待任何子进程
if (pid > 0) {
if (WIFEXITED(status)) {
printf("Child with PID %ld exited with status %d.\n", (long)pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child with PID %ld killed by signal %d.\n", (long)pid, WTERMSIG(status));
}
n--; // 一个子进程已经结束,减少计数
} else if (pid == -1) {
perror("waitpid");
exit(1);
}
}
printf("All children have exited.\n");
return 0;
}
根据输出内容全部回收完成。
好了,关于进程原语就先回顾到这了,其实还有很多需要学习的地方……