标签:新建 失败 运行 abort ima 结束 接收 include 特定
什么是程序?什么是进程?
程序和进程有什么联系?
进程三态模型:就绪、阻塞、运行。
进程五态模型:与三态模型相比,多了新建、终止两种状态。
每个进程都有一个非负整数的进程ID(pid),作为识别不同进程的唯一标识。
此外,每个进程还有一些其他标识符,包括父进程ID(ppid)、实际用户ID(uid)、有效用户ID(euid)、实际组ID(gid)、有效组ID(egid)。
#include <unistd>
pid_t getpid(); //返回值:调用进程的进程ID
pid_t getppid(); //返回值:调用进程的父进程ID
uid_t getuid(); //返回值:调用进程的实际用户ID
uid_t geteuid(); //返回值:调用进程的有效用户ID
uid_t getgid(); //返回值:调用进程的实际组ID
uid_t getegid(); //返回值:调用进程的有效组ID
一个现有的进程可以调用fork函数创建一个新进程,这个新进程叫做子进程,调用fork的进程叫做父进程。
#include <unistd.h>
pid_t fork(); //若成功:子进程返回0,父进程返回子进程ID;若出错,返回-1
fork的特点为:一次调用,两次返回。
fork有以下两种用法:
fork成功返回后,父进程和子进程继续执行后面的代码,但父子进程谁先执行,这点是不确定的。
#include <stdio.h>
#include <unistd.h>
int globvar = 10;
int main()
{
int var = 5;
pid_t pid = fork();
if (pid > 0)
{
sleep(2); //父进程休眠2秒,让子进程先运行
}
else if (pid == 0)
{
globvar++;
var++;
}
printf("pid = %d, globvar = %d, var = %d\n", getpid(), globvar, var);
return 0;
}
正常终止方式:
异常终止方式:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status); //成功返回终止子进程ID,失败返回-1
当在父进程中调用了wait时:
如果wait的参数status不为NULL,那么子进程的终止状态就存放在它指向的内存中,如果不关心终止状态,可以将status指定为NULL。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options); //成功返回终止子进程ID,失败返回-1或0
waitpid的第二个参数status用法和wait一样,但waitpid相比于wait的不同之处在于:
pid > 0
时,waitpid可以等待由pid指定的特定子进程options == WNOHANG
时,若pid指定的子进程尚未终止,waitpid不会阻塞,而是立即返回0pid == -1 && options == 0
时,waitpid等价于wait虽然waitpid可以实现非阻塞版本的wait,但也存在一个缺陷:如果子进程在父进程waitpid(pid, NULL, WNOHANGE)
之后才终止,那么即使父进程尚未结束,也不会给子进程收尸,也就是说,终止的子进程会一直处于僵尸进程状态,直到父进程退出。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork();
if (pid == 0)
{
sleep(2); //确保父进程执行waitpid时子进程还在休眠
printf("child %d exit\n", getpid());
exit(0);
}
if (waitpid(pid, NULL, WNOHANG) == 0)
{
printf("waitpid return before child exit\n");
}
sleep(300); //虽然waitpid不阻塞,但在父进程终止前,子进程pid会一直是僵尸进程
return 0;
}
如果既要保证父进程不阻塞等待子进程终止,也不希望子进程处于僵尸状态直到父进程终止,可以采用调用两次fork的诀窍,其核心思路为:
waitpid(pid, NULL, 0)
,等待进程B终止exit(0)
终止(必须确保进程B在进程C之前终止)#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid = fork();
if (pid == 0)
{
if ((pid = fork()) > 0)
{
exit(0); //子进程B再次调用fork后立即终止,只留下孙子进程C
}
/*孙子进程C由init进程收养*/
sleep(2);
printf("I'm second child %d, my parent bacomes %d\n", getpid(), getppid());
exit(0);
}
printf("I'm parent %d\n", getpid());
if (waitpid(pid, NULL, 0) == pid) //父进程A立即调用waitpid等待子进程B终止
{
printf("first child %d exit\n", pid);
}
/*此时,进程A和进程C之间就没有了继承关系,两者相互独立,互不干扰,各司其职*/
sleep(300);
return 0;
}
从上图执行结果可以看出:
上面提到过fork的两种用法,其中一种是“子进程从fork返回后立即调用exec,执行另一个不同的程序”。
有7个不同的exec函数可供使用,它们统称为exec函数族,可以根据需要调用这7个函数中的任意一个。
#include <unistd.h>
/*7个函数返回值均为:若成功,不返回;若失败,返回-1*/
//以路径名为参数
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[]*/);
int execve(const char *path, char *const argv[], char *const envp[]);
//以文件名为参数
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execvp(const char *file, char *const argv[]);
//以文件描述符为参数
int fexecve(int fd, char *const argv[], char *const envp[]);
这些函数的命名是有规律的:exec[必选:l or v][可选:p or e](fexecve作为特例单独拎出来)
这些函数的参数用于指定新程序的相关信息,分为3部分:可执行程序、命令行参数、环境变量,具体使用规则为:
【必选项l or v】
【可选项p】
【可选项e】
【fexecve特例】
/* filename - execl.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork();
if (pid == 0)
{
execl("/bin/echo", "echo", "executed by execl", NULL);
}
waitpid(pid, NULL, 0);
return 0;
}
/* filename - execv.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
char *execv_argv[] =
{
"echo",
"executed by execv",
NULL
};
int main()
{
pid_t pid = fork();
if (pid == 0)
{
execv("/bin/echo", execv_argv);
}
waitpid(pid, NULL, 0);
return 0;
}
/* filename - execlp.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork();
if (pid == 0)
{
execlp("echo", "echo", "executed by execlp", NULL);
}
waitpid(pid, NULL, 0);
return 0;
}
/* filename - execvp.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
char *execvp_argv[] =
{
"echo",
"executed by execvp",
NULL
};
int main()
{
pid_t pid = fork();
if (pid == 0)
{
execvp("echo", execvp_argv);
}
waitpid(pid, NULL, 0);
return 0;
}
/* filename - execle.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
char *env[] =
{
"PATH=/home/delphi",
"USER=execle",
NULL
};
int main()
{
pid_t pid = fork();
if (pid == 0)
{
execle("/usr/bin/env", "env", NULL, env);
}
waitpid(pid, NULL, 0);
return 0;
}
/* filename - execve.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
char *execve_argv[] =
{
"env",
NULL
};
char *env[] =
{
"PATH=/home/delphi",
"USER=execve",
NULL
};
int main()
{
pid_t pid = fork();
if (pid == 0)
{
execve("/usr/bin/env", execve_argv, env);
}
waitpid(pid, NULL, 0);
return 0;
}
#include <stdlib.h>
int system(const char *command);
在Unix系统中,system在其内部实现调用了fork、exec和waitpid,因此有3种返回值。
下面通过一个system的简易实现,来帮助理解该函数的返回值。
int system(const char * cmdstring)
{
pid_t pid;
int status;
if (cmdstring == NULL)
{
return (1); //如果cmdstring为空,返回非零值,一般为1
}
if ((pid = fork()) < 0)
{
status = -1; //fork失败,返回-1
}
else if (pid == 0)
{
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~
}
else //父进程
{
while (waitpid(pid, &status, 0) < 0)
{
if (errno != EINTR)
{
status = -1; //如果waitpid被信号中断,则返回-1
break;
}
}
}
return status; //如果waitpid成功,则返回子进程的返回状态
}
仔细看完这个system函数的简单实现,该函数的返回值就清晰了吧,那么什么时候system()返回0呢?答案是只在command命令返回0时。
#include <stdlib.h>
int main()
{
system("ls -l");
system("cat func.c");
system("gcc -o func.out func.c");
system("ls -l");
system("echo main.out begin system func.out");
system("./func.out");
return 0;
}
标签:新建 失败 运行 abort ima 结束 接收 include 特定
原文地址:https://www.cnblogs.com/songhe364826110/p/11432253.html