3.4.9.exec族函数及实战1
3.4.9.1、为什么需要exec函数
(1) fork子进程是为了执行新程序(fork创建了子进程后,子进程和父进程同时被OS调度执行,因此子进程可以单独的执行一个程序,这个程序宏观上将会和父进程程序同时进行)
(2)可以直接在子进程的if中写入新程序的代码。这样可以,但是不够灵活,因为我们只能把子进程程序的源代码贴过来执行(必须知道源代码,而且源代码太长了也不好控制)(kill -9 +pid 发信号关掉子进程 ),
代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
/* 接收 fork 的返回值 */
pid_t pid = -1;
/* 接收 wait waitpid 的返回值 */
pid_t ret = -1;
/* wait waitpid 传参,进程状态信息 */
int status = -1;
/* fok 返回一个 进程的 pid */
pid = fork(); //fork 函数返回 2 次 : 等于0 是子进程, 大于0是父进程
if(0 == pid)
{
int cnt = 0;
/* 这里一定是 子进程 , 子进程先结束,成为僵尸进程 ,一定要保证子进程先结束 */
printf("子进程 child pid = %d \n", getpid());
/* 在子进程 中执行一段程序 */
while(1)
{
printf("子进程程序cnt = %d \n", cnt);
sleep(1); /* 休息 1s */
cnt ++;
}
return 99;
}
else if (pid > 0)
{
/* 父进程 加入 sleep */
sleep(1);
printf(" 父进程中打印 子进程id = %d \n ",pid); /* 这里一定是 父进程 */
/* 这里一定是 父进程 */
ret = waitpid(pid,&status,WNOHANG); /* waitpid ,-1表示不管是那个子进程都回收 ,WNOHANG 表示 非阻塞式 */
printf("子进程已经被回收,子进程pid = %d \n", ret);
printf("子进程是否正常退出:%d \n", WIFEXITED(status)); /* WIFEXITED 判断是否正常退出 */
printf("子进程是否 非正常退出:%d \n", WIFSIGNALED(status)); /* WIFSIGNALED 判断是否 非正常退出 */
printf("子进程正常终止情况下的 进程返回值:%d \n", WEXITSTATUS(status)); /* WEXITSTATUS 正常终止情况下的 进程返回值 */
}
else
{
/* 这里一定是 fork出错了 */
perror("fork");
return -1;
}
return 0;
}
运行结果:
这种 在子进程中 执行 一段程序也要缺点:
譬如说我们希望子进程来执行ls -la 命令就不行了(没有源代码,只有编译好的可执行程序)
(3 )使用exec族运行新的可执行程序(exec族函数可以直接把一个编译好的可执行程序直接加载运行)
(4)我们有了exec族函数后,我们典型的父子进程程序是这样的:子进程需要运行的程序被单独编写、单独编译连接成一个可执行程序(叫hello),(项目是一个多进程项目)主程序为父进程,fork创建了子进程后在子进程中exec来执行hello,达到父子进程分别做不同程序同时(宏观上)运行的效果。
3.4.9.2、exec族的6个函数介绍
(1)execl和execv 这两个函数是最基本的exec,都可以用来执行一个程序,区别是传参的格式不同。
execl是把参数列表(本质上是多个字符串,必须以NULL结尾)依次排列而成(l其实就是list的缩写),
execv是把参数列表事先放入一个字符串数组中,再把这个字符串数组传给execv函数。
(2) execlp和execvp 这两个函数在上面2个基础上加了p,较上面2个来说,区别是:上面2个执行程序时必须指定可执行程序的全路径(如果exec没有找到path这个文件则直接报错),而加了p的传递的可以是file(也可以是path,只不过兼容了file。加了p的这两个函数会首先去找file,如果找到则执行执行,如果没找到则会去环境变量PATH所指定的目录下去找,如果找到则执行如果没找到则报错)
(3)execle和execvpe 这两个函数较基本exec来说加了e,函数的参数列表中也多了一个字符串数组envp形参,e就是environment环境变量的意思,和基本版本的exec的区别就是:执行可执行程序时会多传一个环境变量的字符串数组给待执行的程序。
3.4.9.3、exec实战1
(1)使用execl运行ls -l -a
(2)使用execv运行ls
(3)使用execl运行自己写的程序
实例 1 (1)使用execl运行ls -l -a
代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
/* 接收 fork 的返回值 */
pid_t pid = -1;
/* 接收 wait waitpid 的返回值 */
pid_t ret = -1;
/* wait waitpid 传参,进程状态信息 */
int status = -1;
/* fok 返回一个 进程的 pid */
pid = fork(); //fork 函数返回 2 次 : 等于0 是子进程, 大于0是父进程
if(0 == pid)
{
/* 这里一定是 子进程 , 子进程先结束,成为僵尸进程 ,一定要保证子进程先结束 */
printf("子进程 child pid = %d \n", getpid());
/* 在子进程 中执行 ls 命令*/
execl("/bin/ls", "ls", "-l", "-a", NULL); /* "/bin/ls" :命令的全路径。 "ls": 这个是命令 ! "-l","-a", 第3、4个可以是参数,5个、6个...参数也可以,必须以NULL结尾 */
return 99;
}
else if (pid > 0)
{
printf(" 父进程中打印 子进程id = %d \n ",pid); /* 这里一定是 父进程 */
/* 这里一定是 父进程 */
ret = waitpid(pid,&status,WNOHANG); /* waitpid ,-1表示不管是那个子进程都回收 ,WNOHANG 表示 非阻塞式 */
printf("子进程已经被回收,子进程pid = %d \n", ret);
printf("子进程是否正常退出:%d \n", WIFEXITED(status)); /* WIFEXITED 判断是否正常退出 */
printf("子进程是否 非正常退出:%d \n", WIFSIGNALED(status)); /* WIFSIGNALED 判断是否 非正常退出 */
printf("子进程正常终止情况下的 进程返回值:%d \n", WEXITSTATUS(status)); /* WEXITSTATUS 正常终止情况下的 进程返回值 */
}
else
{
/* 这里一定是 fork出错了 */
perror("fork");
return -1;
}
运行结果:
实例 (2)使用execv运行ls
代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
/* 接收 fork 的返回值 */
pid_t pid = -1;
/* 接收 wait waitpid 的返回值 */
pid_t ret = -1;
/* wait waitpid 传参,进程状态信息 */
int status = -1;
/* fok 返回一个 进程的 pid */
pid = fork(); //fork 函数返回 2 次 : 等于0 是子进程, 大于0是父进程
if(0 == pid)
{
/* execv 定义字符串数组 传参 */
char * const arg[] = {"ls", "-l", "-a", NULL};
/* 这里一定是 子进程 , 子进程先结束,成为僵尸进程 ,一定要保证子进程先结束 */
printf("子进程 child pid = %d \n", getpid());
/* 在子进程 中执行 ls 命令*/
//execl("/bin/ls", "ls", "-l", "-a", NULL); /* "/bin/ls" :命令的全路径。 "ls": 这个是命令 ! "-l","-a", 第3、4个可以是参数,5个、6个...参数也可以,必须以NULL结尾 */
execv("/bin/ls", arg);
return 99;
}
else if (pid > 0)
{
printf(" 父进程中打印 子进程id = %d \n ",pid); /* 这里一定是 父进程 */
/* 这里一定是 父进程 */
ret = waitpid(pid,&status,WNOHANG); /* waitpid ,-1表示不管是那个子进程都回收 ,WNOHANG 表示 非阻塞式 */
printf("子进程已经被回收,子进程pid = %d \n", ret);
printf("子进程是否正常退出:%d \n", WIFEXITED(status)); /* WIFEXITED 判断是否正常退出 */
printf("子进程是否 非正常退出:%d \n", WIFSIGNALED(status)); /* WIFSIGNALED 判断是否 非正常退出 */
printf("子进程正常终止情况下的 进程返回值:%d \n", WEXITSTATUS(status)); /* WEXITSTATUS 正常终止情况下的 进程返回值 */
}
else
{
/* 这里一定是 fork出错了 */
perror("fork");
return -1;
}
return 0;
}
运行结果:
实例 3 (3)使用execl运行自己写的程序
代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
/* 接收 fork 的返回值 */
pid_t pid = -1;
/* 接收 wait waitpid 的返回值 */
pid_t ret = -1;
/* wait waitpid 传参,进程状态信息 */
int status = -1;
/* fok 返回一个 进程的 pid */
pid = fork(); //fork 函数返回 2 次 : 等于0 是子进程, 大于0是父进程
if(0 == pid)
{
/* execv 定义字符串数组 传参 */
char * const arg[] = {"ls", "-l", "-a", NULL};
/* 这里一定是 子进程 , 子进程先结束,成为僵尸进程 ,一定要保证子进程先结束 */
printf("子进程 child pid = %d \n", getpid());
/* 在子进程 中执行 ls 命令*/
//execl("/bin/ls", "ls", "-l", "-a", NULL); /* "/bin/ls" :命令的全路径。 "ls": 这个是命令 ! "-l","-a", 第3、4个可以是参数,5个、6个...参数也可以,必须以NULL结尾 */
//execv("/bin/ls", arg);
execl("hello","hello",NULL); /* 第一个"hello"是当前目录下的路径和可执行文件名, 第2个"hello"是命令名字,最后以NULL结尾 */
return 99;
}
else if (pid > 0)
{
printf(" 父进程中打印 子进程id = %d \n ",pid); /* 这里一定是 父进程 */
/* 这里一定是 父进程 */
ret = waitpid(pid,&status,WNOHANG); /* waitpid ,-1表示不管是那个子进程都回收 ,WNOHANG 表示 非阻塞式 */
printf("子进程已经被回收,子进程pid = %d \n", ret);
printf("子进程是否正常退出:%d \n", WIFEXITED(status)); /* WIFEXITED 判断是否正常退出 */
printf("子进程是否 非正常退出:%d \n", WIFSIGNALED(status)); /* WIFSIGNALED 判断是否 非正常退出 */
printf("子进程正常终止情况下的 进程返回值:%d \n", WEXITSTATUS(status)); /* WEXITSTATUS 正常终止情况下的 进程返回值 */
}
else
{
/* 这里一定是 fork出错了 */
perror("fork");
return -1;
}
return 0;
}
运行结果:
更改上边大代码: 父进程 回收 子进程,
代码: 3.4.9.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
/* 接收 fork 的返回值 */
pid_t pid = -1;
/* 接收 wait waitpid 的返回值 */
pid_t ret = -1;
/* wait waitpid 传参,进程状态信息 */
int status = -1;
/* fok 返回一个 进程的 pid */
pid = fork(); //fork 函数返回 2 次 : 等于0 是子进程, 大于0是父进程
if(0 == pid)
{
/* execv 定义字符串数组 传参 */
char * const arg[] = {"ls", "-l", "-a", NULL};
/* 这里一定是 子进程 , 子进程先结束,成为僵尸进程 ,一定要保证子进程先结束 */
printf("子进程 child pid = %d \n", getpid());
/* 在子进程 中执行 ls 命令*/
//execl("/bin/ls", "ls", "-l", "-a", NULL); /* "/bin/ls" :命令的全路径。 "ls": 这个是命令 ! "-l","-a", 第3、4个可以是参数,5个、6个...参数也可以,必须以NULL结尾 */
//execv("/bin/ls", arg);
execl("hello","hello",NULL); /* 第一个"hello"是当前目录下的路径和可执行文件名, 第2个"hello"是命令名字,最后以NULL结尾 */
return 99;
}
else if (pid > 0)
{
/* 加入 sleep , 让子进程 先运行结束,子进程成为僵尸进程, */
sleep(1);
printf(" 父进程中打印 子进程id = %d \n ",pid); /* 这里一定是 父进程 */
/* 这里一定是 父进程 */
ret = waitpid(pid,&status,0); /* waitpid ,-1表示不管是那个子进程都回收 ,0 表示 阻塞式 */
printf("子进程已经被回收,子进程pid = %d \n", ret);
printf("子进程是否正常退出:%d \n", WIFEXITED(status)); /* WIFEXITED 判断是否正常退出 */
printf("子进程是否 非正常退出:%d \n", WIFSIGNALED(status)); /* WIFSIGNALED 判断是否 非正常退出 */
printf("子进程正常终止情况下的 进程返回值:%d \n", WEXITSTATUS(status)); /* WEXITSTATUS 正常终止情况下的 进程返回值 */
}
else
{
/* 这里一定是 fork出错了 */
perror("fork");
return -1;
}
return 0;
}
代码 hello.c
#include <stdio.h>
int main(int argc, char **argv)
{
int i = 0;
/* 打印 argc 的参数 */
printf("argc = %d \n",argc);
/* 打印 argv 的参数 */
while(NULL != argv[i])
{
printf("argv[%d] = %s \n", i, argv[i]);
i++;
}
return 0;
}
运行结果:
代码来自 朱老师 物联网大讲堂