#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include <sys/wait.h>
extern char* *environ;
int showenv()
{
char **p = environ;
while(*p){
fprintf(stderr, "%s\n", *p);
p++;
}
}
//函数fork创建新进程, 函数exec执行新程序, 函数sleep休眠进程, 函数wait同步进程和函数exit结束进程.
int testfork()
{
pid_t pid;
if((pid = fork()) == 0){
fprintf(stderr, "---- begin ----\n");
sleep(10); // 睡眠3秒会导致子进程成为僵死进程
execl("/bin/uname", "uname", "-a", 0);
fprintf(stderr, "---- end ----\n");
}
else if(pid > 0)
fprintf(stderr, "fork child pid = [%d]\n", pid);
else
fprintf(stderr, "Fork failed.\n");
return 0;
}
//exec家族命名规律: 字母 l 表示变长命令行参数: 字母 v 表示函数采用指针数组命令行参数: 字母 P表示函数采用 PATH 变量查找程序: 字母 e 表示函数使用 envp 显示传递环境变。
//最终是调用execve系统调用
int testexec()
{
//执行 ls -l /root
//extern char **environ;
char *argv[] = {"-l","/root",0} ;
execl("/bin/ls","-l","/root",0) ;
//exec后 后面的代码都不会执行
execle("/bin/ls"," -l","/root",0,environ) ;
execlp("ls", "-l", "/root",0) ;
execv("/bin/ls", argv);
execve ("/bin/ls" , argv, environ) ;
execvp("ls" , argv) ;
}
/*vfork比起fork函数更快, 二者的区别如下:
a) vfork创建的子进程并不复制父进程的数据, 在随后的exec调用中系统会复制新程序的数据到内存, 继而避免了一次数据复制过程
b) 父进程以vfork方式创建子进程后将被阻塞, 知道子进程退出或执行exec调用后才能继续运行.
当子进程只用来执行新程序时, vfork-exec模型比fork-exec模型具有更高的效率, 这种方法也是Shell创建新进程的方式.
函数system会阻塞调用它的进程, 并执行字符串string中的shell命令.他和vfork-exec模型是一样的
*/
int testvfork()
{
pid_t pid;
if((pid = vfork()) == 0){
fprintf(stderr, "---- begin ----\n");
sleep(3);
execl("/bin/uname", "uname", "-a", 0);
fprintf(stderr, "---- end ----\n");
}
else if(pid > 0)
fprintf(stderr, "fork child pid = [%d]\n", pid);
else
fprintf(stderr, "Fork failed.\n");
return 0;
}
//僵尸进程
//查看僵尸进程:
//ps -ef | grep 13707(父进程id) 其中, 'defunct'代表僵死进程.
/**
预防僵死进程:
(1) wait法
父进程主动调用wait接收子进程的死亡报告, 释放子进程占用的系统进程表资源.
(2) 托管法
如果父进程先于子进程而死亡, 则它的所有子进程转由进程init领养, 即它所有子进程的父进程ID号变为1. 当子进程结束时init为其释放进程表资源.
(3) 忽略SIGC(H)LD信号
当父进程忽略SIGC(H)LD信号后, 即使不执行wait, 子进程结束时也不会产生僵死进程.
(4) 捕获SIGC(H)LD信号
当父进程捕获SIGC(H)LD信号, 并在捕获函数代码中等待(wait)子进程
*/
int testszobm()
{
pid_t pid;
if((pid = fork()) == 0){
printf("child[%d]\n", getpid());
exit(0);
}
// wait();
printf("parent[%d]\n", getpid());
sleep(10);//父进程不退出,不处理子进程的信号。子进程会成为僵尸进程。
return 0;
}
void ClearChild(int nSignal){
pid_t pid;
int nState;
// WNOHANG非阻塞调用waitpid, 防止子进程成为僵死进程
while((pid = waitpid(-1, &nState, WNOHANG)) > 0);
//程序在接收到SIGCLD信号后立即执行函数ClearChild, 并调用非阻塞的waitpid函数结束子进程结束信息, 如果结束到子进程结束信息则释放该子进程占用的进程表资源,
//否则函数立刻返回. 这样既保证了不增加守护进程负担, 又成功地预防了僵死进程的产生.
signal(SIGCLD, ClearChild); // 重新绑定 SIGCLD信号
}
int InitServer(){
pid_t pid;
assert((pid = fork()) >= 0); // 创建子进程
if(pid != 0){ // 父进程退出, 子进程被init托管
sleep(1);
exit(0);
}
assert(setsid() >= 0); // 子进程脱离终端
umask(0); // 清除文件创建掩码
signal(SIGINT, SIG_IGN); // 忽略SIGINT信号
signal(SIGCLD, ClearChild); // 处理SIGCLD信号,预防子进程僵死
return 0;
}
//守护进程是一个在后台长期运行的进程, 它们独立于控制终端, 周期性地执行某项任务, 或者阻塞直到事件发生, 默默地守护着计算机系统的正常运行.
int testdamo()
{
InitServer();
sleep(100);
}
int main(int argc, char *argv[])
{
int n;
showenv();
//testfork();
//testexec();
//testvfork();
//testszobm();
testdamo();
scanf("%d",&n);
printf("%d\n",n);
return 0;
}
Linux c fork进程实践
最新推荐文章于 2022-05-07 16:34:41 发布