程序的加载和运行
问题:hello程序的加载和运行过程是怎样的?
step1:在shell命令行提示符后输入命令:$./hello,然后按下回车键
step2:在shell命令行解释器构造argv(参数列表)和envp(环境变量列表)
step3:调用fork()函数,创建一个子进程,和父进程shell完全相同(只读/共享),包括只读代码段、可读写数据段、堆以及用户栈等。
step4:调用execve()函数,在当前进程(新创建的子进程)的上下文中加载并运行hello程序。将hello中的.text节、.data节、.bss节等内容加载到当前进程的虚拟地址空间(仅修改当前进程上下文中关于存储映像的一些数据结构,不从磁盘拷贝代码和数据等内容)
step5:调用hello程序的main()函数,hello程序开始在一个进程的上下文中运行。
fork的基本知识
函数原型:pid_t fork(void);
返回值:若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1。
一个现有进程可以调用fork函数创建一个新进程。由fork函数创建的新进程被称为子进程。
fork函数例子
fork0.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (fork() == 0) {
printf("Hello from child\n");
}
else {
printf("Hello from parent\n");
}
return 0;
}
输出结果:
进程图如下:
获取进程ID
每个进程都有一个唯一的正数(非零)进程ID(PID)。
getpid函数返回调用进程的PID。
getppid函数返回它的父进程的PID(创建调用进程的进程)。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
fork1.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int x = 1;
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
}
else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
return 0;
}
运行结果:
进程图:
fork2.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("Bye\n");
return 0;
}
运行结果:
进程图:
fork4.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("L0\n");
if (fork() != 0) {
printf("L1\n");
if (fork() != 0) {
printf("L2\n");
}
}
printf("Bye\n");
return 0;
}
运行结果:
进程图:
fork5.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("L0\n");
if (fork() == 0) {
printf("L1\n");
if (fork() == 0) {
printf("L2\n");
}
}
printf("Bye\n");
return 0;
}
运行结果:
进程图:
atexit()函数
函数声明:int atexit(void (*func)(void));
atexit()函数详解
fork6.c
#include <stdlib.h>// 使用atexit()函数必须包含的头文件stdlib.h
#include <stdio.h>
#include <unistd.h>
void cleanup(void) {
printf("Cleaning up\n");
}
int main(int argc, char *argv[])
{
atexit(cleanup);
fork();
exit(0);
}
运行结果:
进程图:
回收子进程
当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态,直到被它的父进程回收。
一个终止了但还未被回收的进程称为僵死进程。
fork7.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (fork() == 0) {
/* Child */
printf("Terminating Child, PID = %d\n", getpid());
exit(0);
} else {
printf("Running Parent, PID = %d\n", getpid());
while (1); /* Infinite loop */
}
}
运行结果:
因为在父进程中有while(1); ,所以父进程没法结束,一直在死循环,回不到shell命令行
我们用CTRL+Z将其挂起在后台运行
输入ps查看进程状态
我们发现子进程是defunct状态(僵尸状态),我们尝试用kill来杀死它
输入如下命令
$kill -9 4117
再用ps查看进程状态
发现没被杀死
所谓斩草要除根,擒贼先擒王
我们来杀死它的爸爸(僵尸进程的父进程)
发现僵尸成功扫除干净 ~ ( ̄▽ ̄)~*
我们再来看一个子进程死循环的情况
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (fork() == 0) {
/* Child */
printf("Running Child, PID = %d\n",getpid());
while (1); /* Infinite loop */
} else {
printf("Terminating Parent, PID = %d\n",
getpid());
exit(0);
}
}
运行结果:
我们发现子进程一直在运行,没有终止
爸爸不等儿子直接把儿子抛弃了〒▽〒
但是没关系,我们可以叫父亲等等他的儿子( •̀ ω •́ )✧
一个进程可以调用waitpid函数来等待它的子进程终止或者停止。
wait函数和waitpid函数解析
pid_t wait(int *statusp);是waitpid函数的简化版本,调用wait(&status)等价于调用waitpid(-1,&statusp,0)
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *statusp, int options);
fork9.c
int main()
{
int child_status;
if (fork() == 0) {
printf("HC: hello from child\n");
exit(0);
} else {
printf("HP: hello from parent\n");
wait(&child_status); //调用wait函数,等到子进程终止了就执行下一条语句
printf("CT: child has terminated\n"); //这条语句一定是在HC的后面出现
}
printf("Bye\n");
}
运行结果:
fork10.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#define N 5
int main(int argc, char *argv[])
{
int i, child_status;
pid_t pid;
for (i = 0; i < N; i++)
if ((pid = fork()) == 0) {
printf("%d已结束\n",i);
exit(100+i); /* Child */
}
while ((pid = wait(&child_status))>0) { /* Parent */
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
pid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", pid);
}
}
运行结果:
它使用waitpid,不按照特定的顺序等待它的所有N个子进程终止。父进程创建 N个子进程,每个子进程以一个唯一的退出状态退出。
父进程用 waitpid作为while循环的测试条件,等待它所有的子进程终
止。因为第一个参数是-1,所以对waitpid的调用会阻塞,直到任意一个子进程终止。
在每个子进程终止时,对waitpid的调用会返回,返回值为该子进程的非零的PID。if (WIFEXITED(child_status)) 检查子进程的退出状态。如果子进程是正常终止的——在此是以调用 exit 函数终止的——那么父进程就提取出退出状态,把它输出到stdout上。
我们做一个简单的改变,下面这个.c文件消除了这种不确定性,按照父进程创建子进程的相同顺序来回收这些子进程。
fork11.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#define N 5
int main(int argc, char *argv[])
{
int i, child_status;
pid_t wpid,pid[N];
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
printf("%d已结束\n",i);
exit(100+i); /* Child */
}
i = 0;
while ((wpid = waitpid(pid[i++],&child_status,0))>0) { /* Parent */
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
运行结果:
持续更新…