exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段、和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。
如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样。
execv("./B",envp);
注意:不管file,第一个参数必须是可执行文件的名字
可执行文件查找方式
表中的前四个函数的查找方式都是指定完整的文件目录路劲,而最后两个函数(以p结尾的函数)可以只给出文件名,系统会自动从环境变量"$PATH"所包含的路径中进行查找。
参数表传递方式
两种方式:一个一个列举和将所有参数通过指针数组传递
一函数名的第5个字母按来区分,字母"l"(list)的表示一个一个列举方式;字母"v"(vector)的表示将所有参数构造成指针数组传递,其语法为char *const argv[]
环境变量的使用
exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。这里,以"e"(Envirment)结尾的两个函数execle、execve就可以在envp[]中传递当前进程所使用的环境变量。
使用的区别
可执行文件查找方式
参数表传递方式
环境变量的使用
案例一execl
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("start to execl.\n");
if(execl("/bin/ls","ls",NULL) < 0)
{
perror("Fail to execl");
return -1;
}
printf("end of execl.\n");
return 0;
}
运行结果如下:
案例二、execlp
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("start to execl.\n");
if(execlp("ls","ls","-l",NULL) < 0)
{
perror("Fail to execl");
return -1;
}
printf("end of execl.\n");
return 0;
}
运行结果:
案例三、execle
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
if(getenv("B") == NULL)
{
printf("fail to getenv B.\n");
}else{
printf("env B = %s.\n",getenv("B"));
}
if(getenv("C") == NULL)
{
printf("fail to getenv C.\n");
}else{
printf("env C = %s.\n",getenv("C"));
}
if(getenv("PATH") == NULL)
{
printf("fail to getenv PATH.\n");
}else{
printf("env PATH = %s.\n",getenv("PATH"));
}
return 0;
}
运行结果:
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("start to execle.\n");
char * const envp[] = {"B=hello",NULL};
if(execle("./A.out","A.out",NULL,envp) < 0)
{
perror("Fail to execl");
return -1;
}
printf("end of execl.\n");
return 0;
}
运行结果:
案例四:execv
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
char * const arg[] = {"ps", "-ef", NULL};
//if (execl("/bin/ps", "ps", "-ef", NULL) < 0)
if (execv("/bin/ps" ,arg) < 0)
{
perror("execl");
exit(-1);
}
while (1);
return 0;
}
五、进程的创建vfork()函数
vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空完全复制到子进程中,因为子进程会立即调用exec(或exit)于是也就不会存、访该地址空间。不过在子进程调用exec或exit之前,它在父进程的空间中运行。
vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后 父进程才可能被调度运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁)
探究1.vfork()
编译运行:
因为我们知道vfork保证子进程先运行,子进程运行结束后,父进程才开始运行。所以,第一次打印的是子进程的打印的信息,可以看到var值变成了89。子进程结束后,父进程运行,父进程首先打印fork调用返回给他pid的值(就是子进程pid)。以上我们可以看出,vfork创建的子进程和父进程运行的地址空间相同(子进程改变了var 值,父进程中的var值也进行了改变)。
注意:如果子进程中执行的是exec函数,那就是典型的fork的copy-on-wirte。
五、wait和waitpid
wait函数:调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回。
waitpid函数:功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式(阻塞或非阻塞)。
wait函数
#include <sys/types.h>
#include <sys/waith.h>
pid_t wait(int *status);
函数参数:
status是一个整型指针,指向的对象用来保存子进程退出时的状态。
A.status若为空,表示忽略子进程退出时的状态
B.status若不为空,表示保存子进程退出时的状态
子进程的结束状态可由Linux中一些特定的宏来测定。
案例一、
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
if((pid = fork()) < 0)
{
perror("Fail to fork");
return -1;
}else if(pid == 0){
printf("child exit now.\n");
exit(0);
}else{
while(1);
}
exit(0);
}
运行结果:
从以上可以看出,子进程正常退出时,处于僵尸态。这个时候子进程的pid,以及内核栈资源并没有释放,这样是不合理的,我们应该避免僵尸进程。如果父进程先退出呢,子进程又会怎样?
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
if((pid = fork()) < 0)
{
perror("Fail to fork");
return -1;
}else if(pid == 0){
printf("child running now - pid : %d.\n",getpid());
while(1);
}else{
getchar();
printf("Father exit now - pid : %d.\n",getpid());
exit(0);
}
}
从上面可以看出,如果父进程先退出,则子进程的父进程的ID号变为1,也就是说当一个子进程的父进程退出时,这个子进程会被init进程自动收养。
案例二、利用wait等待回收处于僵尸态的子进程
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
if((pid = fork()) < 0)
{
perror("Fail to fork");
return -1;
}else if(pid == 0){
printf("child runing now - pid : %d.\n",getpid());
getchar();
printf("child exiting now - pid : %d.\n",getpid());
exit(0);
}else{
printf("Father wait zombie now - pid : %d.\n",getpid());
wait(NULL);
printf("Father exiting now - pid : %d.\n",getpid());
exit(0);
}
}
没有输入字符前:
输入字符后:
此时我们没有发现僵尸进程,当子进程退出时,父进程的wait回收了子进程未释放的资源。
案例三、获取进程退出时的状态
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
int status;
if((pid = fork()) < 0)
{
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
printf("child process : %d calling exit(7).\n",getpid());
exit(7);
}else{
if((pid = fork()) < 0 ){
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
while(1);
}else{
while((pid = wait(&status)) != -1)
{
if(WIFEXITED(status))
{
printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status));
}else if(WIFSIGNALED(status)){
printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status));
}else{
printf("Not know.\n");
}
}
}
}
printf("All child process is exit,father is exit.\n");
exit(0);
}
给进程15494发个信号
程序运行结果:
从以上探究可以知道,每当子进程结束后,wait函数就会返回哪个子进程结束的pid。如果没有子进程存在,wait函数就返回-1。
函数返回值:
成功:子进程的进程号
失败:-1
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
参数:
1.在父进程中创建两个子进程(A B)
2.A进程打印"child process %d exit",调用exit(2),结束
3.B进程一直运行
注意:父进程调用while(waitpid(-1,&status,WUNTRACED) != -1 )
status:同wait
options:
WNOHANG,若由pid指定的子进程并不立即可用,则waitpid不阻塞,此时返回值为0
WUNTRACED,若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还没报告过,则返回其状态。
0:同wait,阻塞父进程,等待子进程退出。
返回值
正常:结束的子进程的进程号
使用选项WNOHANG且没有子进程结束时:0
调用出错:-1
案例一、
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
int status;
if((pid = fork()) < 0)
{
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
printf("child process : %d calling exit(7).\n",getpid());
exit(7);
}else{
if((pid = fork()) < 0 ){
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
while(1);
}else{
while((pid = wait(&status)) != -1)
{
if(WIFEXITED(status))
{
printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status));
}else if(WIFSIGNALED(status)){
printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status));
}else{
printf("Not know.\n");
}
}
}
}
printf("All child process is exit,father is exit.\n");
exit(0);
}
程序运行结果:
使用ps -aux结果
从以上可以看出,子进程15783退出时,父进程并没有回收它的资源,此时可以看到它处于僵尸态。
由于父进程调用waitpid等待子进程15784退出,此时这个进程还没退出,看可以看到父进程处于可中断的睡眠状态。
我们给子进程15784发个信号,在看看结果
程序运行结果:
可以看到当waitpid指定等待的进程退出时,waitpid立即返回,此时父进程退出。
案例二、
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
int status;
if((pid = fork()) < 0)
{
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
printf("child process : %d calling exit(7).\n",getpid());
exit(7);
}else{
if((pid = fork()) < 0 ){
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
while(1);
}else{
sleep(2);
printf("Father wait child %d exit.\n",pid);
while((pid = waitpid(pid,NULL,WNOHANG)))
{
printf("The process %d is exit.\n",pid);
}
printf("The process %d is exit.\n",pid);
}
}
exit(0);
}
从上面探究我们可以看出,如果有子进程处于僵尸态,waitpid(pid,NULL,WNOHANG)立即处理后返回,如果没有子进程处于僵尸态,此时waitpid(pid,NULL,WNOHANG)也会立即返回,而不阻塞,此时返回值为0。
案例探究三、
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char *argv[])
{
int pid;
int status;
if((pid = fork()) < 0)
{
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
printf("child process in proces group %d.\n",getpgid(0));
printf("child process : %d calling exit(7).\n",getpid());
exit(7);
}else{
if((pid = fork()) < 0 ){
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
sleep(3);
printf("create child process : %d.\n",getpid());
setpgid(0,0); //让子进程属于以自己ID作为组的进程组
printf("child process in proces group %d.\n",getpgid(0));
printf("child process : %d calling exit(6).\n",getpid());
}else{
while(pid = waitpid(0,NULL,0))
{
printf("Father wait the process %d is exit.\n",pid);
}
}
}
exit(0);
}
运行结果:
当在父进中创建子进程时,父进程和子进程都在以父进程ID号为组的进程组。以上代码中,有一个子进程改变了自己所在的进程组.
此时waitpid(0,NULL,0);只能处理以父进程ID为组的进程组中的进程,可以看到第一个子进程结束后waitpid函数回收了它未释放的资源,而第二个子进程则处于僵尸态