【Linux C | 进程】创建进程 | vfork函数+exec函数,以及system函数——文中很多C语言例子帮助理解

news/2024/7/11 1:39:13 标签: linux, c语言, execl, execv, ecexve, vfork, system

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭

本文未经允许,不得转发!!!

目录


在这里插入图片描述

vforkfont__12">🎄一、vfork 函数概述

vforkfont__13">✨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函数的对比:

区别forkvfork
执行两次fork函数之后的代码会执行两次,父进程执行一次,子进程执行一次vfork函数之后的代码会执行两次,父进程执行一次,子进程执行一次
返回两次fork函数返回两次,在父进程返回子进程的进程ID,在子进程返回 0vfork函数返回两次,在父进程返回子进程的进程ID,在子进程返回 0
复制资源fork函数创建的子进程会复制父进程除了代码区之外所有区域(包括数据段、bss段、堆、栈、文件描述符等)vfork 不负责父进程资源
谁先执行调用fork函数后,无法确定是子进程先执行,还是父进程先执行vfork之后,子进程先执行,父进程阻塞直到子进程终止或调用exec系列函数

vforkfont__39">✨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个参数。


execlexecvfont__154">✨2.1 execlexecv 函数举例

#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;
}

execlpexecvpfont__187">✨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;
}

execleexecvefont__228">✨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__execfont__289">🎄三、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成功,则返回子进程的返回状态 
}

在这里插入图片描述

systemfont__323">🎄四、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语言例子帮助理解。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁


http://www.niftyadmin.cn/n/5341331.html

相关文章

如何在Docker上运行Redis

环境: 1.windows系统下的Docker deckstop 1.Pull Redis镜像 2.运行Redis镜像 此时,Redis已经启动&#xff0c;我们登录IDEA查看下是否连接上了 显示连接成功&#xff0c;证明已经连接上Docker上的Redis了

vue封装接口

目录 封装接口前缀 配置逻辑 接口存放文件 配置代理 获取数据方法 封装接口前缀 config.js const serverConfig {baseURL: "https://xxx.xxxxxxxx.com/api", // 请求基础地址,可根据环境自定义useTokenAuthorization: false, // 是否开启 token 认证};export …

力扣日记1.21-【回溯算法篇】77. 组合

力扣日记&#xff1a;【回溯算法篇】77. 组合 日期&#xff1a;2023.1.21 参考&#xff1a;代码随想录、力扣 终于结束二叉树了&#xff01;听说回溯篇也是个大头&#xff0c;不知道这一篇得持续多久了…… 77. 组合 题目描述 难度&#xff1a;中等 给定两个整数 n 和 k&#…

基于SpringBoot Vue二手闲置物品交易系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

CentOS 7 安装配置MySQL

目录 一、安装MySQL​编辑​编辑 1、检查MySQL是否安装及版本信息​编辑 2、卸载 2.1 rpm格式安装的mysql卸载方式 2.2 二进制包格式安装的mysql卸载 3、安装 二、配置MySQL 1、修改MySQL临时密码 2、允许远程访问 2.1 修改MySQL允许任何人连接 2.2 防火墙的问题 2…

《WebKit 技术内幕》学习之十(1): 插件与JavaScript扩展

虽然目前的浏览器的功能很强 &#xff0c;但仍然有其局限性。早期的浏览器能力十分有限&#xff0c;Web前端开发者希望能够通过一定的机制来扩展浏览器的能力。早期的方法就是插件机制&#xff0c;现在流行次啊用混合编程&#xff08;Hybird Programming&#xff09;模式。插件…

vConsole 与 Vue中未定义变量而引发的Maximum call stack size exceeded异常问题

一、问题描述 前段时间有个前端小伙伴反馈在打包发布正式环境后调用VantUI的<van-popup>组件显示时&#xff0c;显示空白&#xff0c;并且在控制台看到一个Maximum call stacksize exceeded&#xff08;超出最大调用堆栈大小&#xff09;,而本地开发环境正常&#xff1a…

在 wsl-ubuntu 里通过 docker 启动 gpu-jupyter

在 wsl-ubuntu 里通过 docker 启动 gpu-jupyter 0. 背景1. 安装 docker-ce2. 安装 NVIDIA Container Toolkit3. 使用 nvidia-ctk 命令配置容器运行4. 通过 docker 运行 nvidia-smi5. 运行 gpu-jupyter6. 访问 gpu-jupyter7. 测试 gpu-jupyter 是否可以访问 cuda 0. 背景 今天突…