😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
本文未经允许,不得转发!!!
目录
- 🎄一、vfork 函数概述
- ✨1.1 vfork 函数介绍
- ✨1.2 vfork 函数举例
- 🎄二、exec 函数
- ✨2.1 execl、execv 函数举例
- ✨2.2 execlp、execvp 函数举例
- ✨2.3 execle、execve 函数举例
- 🎄三、vfork + exec 举例
- 🎄四、system 函数
- 🎄五、总结
🎄一、vfork 函数概述
✨1.1 vfork 函数介绍
函数原型
#include <unistd.h>
pid_t vfork(void);
返回值:父进程返回子进程ID,子进程返回0,出错在父进程返回-1。
vfork 和 fork 函数很像,也是用于创建子进程的,但与fork函数有两点不同:
- 1、vfork 创建的子进程不复制父进程的任何资源,而是直接占用父进程的资源运行代码。子进程不能从当前函数
return
或调用exit()
,但可以调用_exit()
。 - 2、调用 vfork 后,父进程会阻塞,直到子进程终止或调用execcv系列函数。
下表是fork函数和vfork函数的对比:
区别 | fork | vfork |
---|---|---|
执行两次 | fork函数之后的代码会执行两次,父进程执行一次,子进程执行一次 | vfork函数之后的代码会执行两次,父进程执行一次,子进程执行一次 |
返回两次 | fork函数返回两次,在父进程返回子进程的进程ID,在子进程返回 0 | vfork函数返回两次,在父进程返回子进程的进程ID,在子进程返回 0 |
复制资源 | fork函数创建的子进程会复制父进程除了代码区之外所有区域(包括数据段、bss段、堆、栈、文件描述符等) | vfork 不负责父进程资源 |
谁先执行 | 调用fork函数后,无法确定是子进程先执行,还是父进程先执行 | vfork之后,子进程先执行,父进程阻塞直到子进程终止或调用exec系列函数 |
✨1.2 vfork 函数举例
看例子:
1、即使在子进程sleep(3),子进程依旧先运行,父进程一直阻塞到子进程终止;
2、在子进程中修改变量后,父进程打印的变量全是子进程修改的,说明它们共享资源。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int i; // 存在于程序的bss段
int j=100; // 存在于程序的数据段
int main()
{
short s=10; // 存在于栈
char *str = malloc(20); // 存在于堆
strcpy(str, "abcdef");
printf("程序开始执行!\n");
pid_t pid = vfork();
if(pid>0) // 父进程
{
printf("父进程执行过程中.... fatherPid=%d, childPid=%d\n", getpid(), pid);
printf("&i=%p, &j=%p, &s=%p str=%p\n", &i,&j,&s,str);
printf("i=%d, j=%d, s=%d str=[%s]\n", i,j,s,str);
i=1;
j=2;
s=3;
}
else if(pid==0) // 子进程
{
sleep(3);
printf("子进程执行过程中.... fatherPid=%d, childPid=%d\n", getppid(), getpid());
printf("&i=%p, &j=%p, &s=%p str=%p\n", &i,&j,&s,str);
printf("i=%d, j=%d, s=%d str=[%s]\n", i,j,s,str);
i=1;
j=2;
s=3;
strcpy(str, "ABCDEF");
printf("i=%d, j=%d, s=%d str=[%s]\n", i,j,s,str);
//return(0); // 执行报错
//exit(0);
_exit(0);
}
else
{
printf("fork error\n");
}
free(str);
while(1)
sleep(1);
printf("程序执行结束\n");
return 0;
}
执行结果:
最后,说明一下,vfork函数一般是结合exec系列函数一起使用的。那什么是exec系列函数呢?
🎄二、exec 函数
exec
系列函数的主要作用:替代原有的进程的代码段、数据段、BSS段、堆区和栈区,然后从新的main开始执行,但是保留了原子进程的PID。
替换的意思就是,调用进程成功执行完exec函数后,原来进程在exec后面的代码都不会被执行了
。
exec函数原型如下:
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);
这6个函数中,只有execve
是系统调用,另外5个是库函数,它们最终都会调用execve
。
这6个函数怎么记忆、使用:
- 函数名前4个都是
exec
- 第五个字母:
l
:是list
的意思,要求新程序的每个命令行参数都作为单独的一个函数参数来传递;execl("/bin/ls", "ls", "-l", "-a", "./", NULL);
v
:是vector
的意思,要求先构造一个指向各个参数的指针数组,再将该数组作为参数传递给execv*
char *args[]={"ls", "-l", "-a", "./", NULL}; execv("/bin/ls",args);
- 第六个字母:
p
:是path
的意思,如果参数file
包含/
,将将其视为路径名;否则从PATH
环境变量查找可执行文件;execlp("ls", "ls", "-l", "-a", "./", NULL); char *args[]={"ls", "-l", "-a", "./", NULL}; execvp("ls",args);
e
:是environ
的意思,可以给新程序传递一个指向环境字符串指针数组的指针。char *env_init[] = {"USER=wkd_007", "HOME=/home/wkd", NULL}; execle("./my_echo", "my_echo", "-l", "-a", "./", NULL, env_init); char *args[]={"my_echo", "-l", "-a", "./", NULL}; execve("./my_echo", args, env_init);
注意: 使用时,第0个参数也需要传递给exec
函数。例如命令ls -l -a ./
,则ls
是第0个参数。
✨2.1 execl、execv 函数举例
#include <stdio.h>
#include <unistd.h>
#define EXECL_TEST 1
int main()
{
int i = 10;
#if EXECL_TEST
printf("EXECL_TEST \n");
//int res = execl("/bin/ls", "123", "-l", "-a", "./", NULL); // 第0个参数需要传,但exec函数没有使用。
int res = execl("/bin/ls", "ls", "-l", "-a", "./", NULL);
if(res == -1)
{
perror("execl");
}
#else
printf("EXECV_TEST \n");
char *args[]={"ls", "-l", "-a", "./", NULL};
int res = execv("/bin/ls",args);
if(res == -1)
{
perror("execl");
}
#endif
printf("i=%d\n",i); // 不会打印,因为exec函数执行成功后,会替换代码段
return 0;
}
✨2.2 execlp、execvp 函数举例
#include <stdio.h>
#include <unistd.h>
#define EXECLP_TEST 0
int main()
{
int i = 10;
#if EXECLP_TEST
int res = execl("ls", "ls", "-l", "-a", "./", NULL);
if(res == -1)
{
perror("execl"); // 报错:execl: No such file or directory
}
res = execlp("ls", "ls", "-l", "-a", "./", NULL);
if(res == -1)
{
perror("execlp");
}
#else
char *args[]={"ls", "-l", "-a", "./", NULL};
int res = execv("ls",args);
if(res == -1)
{
perror("execv"); // 报错:execv: No such file or directory
}
res = execvp("ls",args);
if(res == -1)
{
perror("execvp");
}
#endif
printf("i=%d\n",i); // 不会打印,因为exec函数执行成功后,会替换代码段
return 0;
}
✨2.3 execle、execve 函数举例
程序一:my_echo,用来打印参数列表和环境表。
// gcc my_echo.c -o my_echo
#include <stdio.h>
#include <unistd.h>
extern char** environ;
int main(int argc, char *argv[])
{
int i=0;
for(i=0; i<argc; i++)
{
printf("argv[%d] = %s\n",i, argv[i]);
}
for (i = 0; *(environ+i)!=NULL; i++)
{
/* echo all command-line args */
printf ( "environ[%02d]: %s \n", i, *(environ+i) );
}
return 0;
}
程序二:
#include <stdio.h>
#include <unistd.h>
char *env_init[] = {"USER=wkd_007", "HOME=/home/wkd", NULL};
#define EXECLE_TEST 1
int main()
{
int i = 10;
#if EXECLE_TEST
printf("EXECLE_TEST:\n");
int res = execle("./my_echo", "my_echo", "-l", "-a", "./", NULL, env_init);
if(res == -1)
{
perror("execle");
}
#else
printf("EXECVE_TEST:\n");
char *args[]={"my_echo", "-l", "-a", "./", NULL};
int res = execve("./my_echo", args, env_init);
if(res == -1)
{
perror("execve");
}
#endif
printf("i=%d\n",i); // 不会打印,因为exec函数执行成功后,会替换代码段
return 0;
}
🎄三、vfork + exec 举例
int my_system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL)
{
return (1); //如果cmdstring为空,返回非零值,一般为1
}
if((pid = vfork())<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 函数
函数原型:
#include <stdlib.h>
int system(const char *command);
system
函数通过调用/bin/sh -c
命令执行command
指定的命令,并在命令完成后返回到当前进程。在执行命令期间,SIGCHLD将被阻止,SIGINT和SIGQUIT将被忽略。
因为system在其实现中调用了fork、exec和waitpid,因此有三种返回值:
1、如果fork
失败,或者waitpid
返回EINTR
之外的错误,则system
返回-1
,且errno
中设置了错误类型值;
2、如果exec
失败(表示不能执行shell),则其返回值如同shell执行了exit(127)一样。
3、否则所有三个函数(fork、exec和waitpid)都执行成功,并且system的返回值是shell的终止状态,其格式已在waitpid中说明。
使用system而不是直接使用fork和exec的优点是:system进行了所需的各种出错处理,以及各种信号处理。但system执行效率低于exec。
例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int res = system("ls -l -a ./");
if(res == -1)
{
perror("system");
}
return 0;
}
🎄五、总结
本文先举例介绍了vfork函数,再举例介绍exec系列函数,最后介绍了system函数,文中提供了很多C语言例子帮助理解。
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁