嵌入式Linux-进程中常用的函数调用

news2025/7/6 1:26:30

1. execve()函数

系统调用 execve()可以将新程序加载到某一进程的内存空间,通过调用 execve()函数将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main()函数开始执行。

#include <unistd.h>

int execve(const char *filename, char *const argv[], char *const envp[]);

参数含义:

参数含义
filename向需要载入当前进程空间的新程序的路径名,既可以是绝对路径、也可以是相对路径
argv参数 argv 则指定了传递给新程序的命令行参数。是一个字符串数组,该数组对应于 main(int argc, char *argv[])函数的第二个参数 argv,且格式也与之相同,是由字符串指针所组成的数组,以 NULL 结束。argv[0]对应的便是新程序自身路径名
envp参数 envp 也是一个字符串指针数组,指定了新程序的环境变量列表,参数 envp 其实对应于新程序的 environ 数组,同样也是以 NULL 结束,所指向的字符串格式为 name=value
返回值execve 调用成功将不会返回;失败将返回-1,并设置 errno

对 execve()的成功调用将永不返回,而且也无需检查它的返回值,实际上,一旦该函数返回,就表明它发生了错误。

基于系统调用 execve(),还提供了一系列以 exec 为前缀命名的库函数,虽然函数参数各异,当其功能相同,通常将这些函数(包括系统调用 execve())称为 exec 族函数,所以 exec 函数并不是指某一个函数、而是 exec 族函数。

下面准备了两个demo案例:
testApp:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
	 char *arg_arr[5];
	 char *env_arr[5] = {"NAME=app", "AGE=25",
	 "SEX=man", NULL};
	 if (2 > argc)
	 exit(-1);
	 arg_arr[0] = argv[1];
	 arg_arr[1] = "Hello";
	 arg_arr[2] = "World";
	 arg_arr[3] = NULL;
	 execve(argv[1], arg_arr, env_arr);
	 perror("execve error");
	 exit(-1);
}

newApp:

#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main(int argc, char *argv[])
{
	 char **ep = NULL;
	 int j;
	 for (j = 0; j < argc; j++)
	 printf("argv[%d]: %s\n", j, argv[j]);
	 puts("env:");
	 for (ep = environ; *ep != NULL; ep++)
	 printf(" %s\n", *ep);
	 exit(0);
}

在这里插入图片描述
由上图打印结果可知,在我们的 testApp 程序中,成功通过 execve()运行了另一个新的程序 newApp,当newApp 程序运行完成退出后,testApp 进程就结束了。

说到这里,我们来分析一个问题,为什么需要在子进程中执行新程序?其实这个问题非常简单,虽然可以直接在子进程分支编写子进程需要运行的代码,但是不够灵活,扩展性不够好,直接将子进程需要运行的代码单独放在一个可执行文件中不是更好吗,所以就出现了 exec 操作。

2. exec全家桶

exec 族函数包括多个不同的函数,这些函数命名都以 exec 为前缀,这一小节我们介绍 exec 族函数中的库函数,这些库函数都是基于系统调用 execve()而实现的,虽然参数各异、但功能相同,包括:execl()、execlp()、execle()、execv()、execvp()、execvpe(),它们的函数原型如下所示:

#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

execl()和 execv()都是基本的 exec 函数,都可用于执行一个新程序,它们之间的区别在于参数格式不同;参数 path 意义和格式都相同,与系统调用 execve()的 filename 参数相同,指向新程序的路径名,既可以是绝对路径、也可以是相对路径。execl()和 execv()不同的在于第二个参数,execv()的argv 参数与 execve()的 argv 参数相同,也是字符串指针数组;而 execl()把参数列表依次排列,使用可变参数形式传递,本质上也是多个字符串,以 NULL 结尾,如下所示:

// execv 传参
char *arg_arr[5];
arg_arr[0] = "./newApp";
arg_arr[1] = "Hello";
arg_arr[2] = "World";
arg_arr[3] = NULL;
execv("./newApp", arg_arr);

// execl 传参
execl("./newApp", "./newApp", "Hello", "World", NULL);

execlp()和 execvp()在 execl()和 execv()基础上加了一个 p,这个 p 其实表示的是 PATH;execl()和execv()要求提供新程序的路径名,而 execlp()和 execvp()则允许只提供新程序文件名,系统会在由环境变量 PATH 所指定的目录列表中寻找相应的可执行文件,如果执行的新程序是一个 Linux 命令,这将很有用;当然,execlp()和 execvp()函数也兼容相对路径和绝对路径的方式。

execle()和 execvpe()这两个函数在命名上加了一个 e,这个 e 其实表示的是 environment 环境变量,意味着这两个函数可以指定自定义的环境变量列表给新程序,参数envp与系统调用execve()的envp参数相同,也是字符串指针数组。

3. exec库函数的使用方法

就拿Linux中一个很简单的shell命令来做举例

ls -al
  1. execl()函数运行 ls 命令
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
	 execl("/bin/ls", "ls", "-a", "-l", NULL);
	 perror("execl error");
	 exit(-1);
}
  1. execv()函数运行 ls 命令。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
	 char *arg_arr[5];
	 arg_arr[0] = "ls";
	 arg_arr[1] = "-a";
	 arg_arr[2] = "-l";
	 arg_arr[3] = NULL;
	 execv("/bin/ls", arg_arr);
	 perror("execv error");
	 exit(-1);
}
  1. execlp()函数运行 ls 命令。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
	 execlp("ls", "ls", "-a", "-l", NULL);
	 perror("execlp error");
	 exit(-1);
}
  1. execvp()函数运行 ls 命令。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
	 char *arg_arr[5];
	 arg_arr[0] = "ls";
	 arg_arr[1] = "-a";
	 arg_arr[2] = "-l";
	 arg_arr[3] = NULL;
	 execvp("ls", arg_arr);
	 perror("execvp error");
	 exit(-1);
}
  1. execle()函数运行 ls 命令
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern char **environ;
int main(void)
{
	 execle("/bin/ls", "ls", "-a", "-l", NULL, environ);
	 perror("execle error");
  	 exit(-1);
}
  1. execvpe()函数运行 ls 命令
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern char **environ;

int main(void)
{
	 char *arg_arr[5];
	 arg_arr[0] = "ls";
	 arg_arr[1] = "-a";
	 arg_arr[2] = "-l";
	 arg_arr[3] = NULL;
	 execvpe("ls", arg_arr, environ);
	 perror("execvpe error");
	 exit(-1);
}

以上全部案例,结果都一样的:
在这里插入图片描述

4. system()函数

使用 system()函数可以很方便地在我们的程序当中执行任意 shell 命令,我们来学习下 system()函数的用法,以及介绍 system()函数的实现方法。

#include <stdlib.h>

int system(const char *command);

参数介绍:

参数含义
command参数 command 指向需要执行的 shell 命令,以字符串的形式提供,譬如"ls -al"、"echo HelloWorld"等

system()的返回值如下:

  1. 当参数 command 为 NULL,如果 shell 可用则返回一个非 0 值,若不可用则返回 0;
  2. 如果无法创建子进程或无法获取子进程的终止状态,那么 system()返回-1;
  3. 如果子进程不能执行 shell,则 system()的返回值就好像是子进程通过调用_exit(127)终止了;
  4. 如果所有的系统调用都成功,system()函数会返回执行 command 的 shell 进程的终止状态;

system()函数其内部的是通过调用 fork()、execl()以及 waitpid()这三个函数来实现它的功能,首先 system()会调用 fork()创建一个子进程来运行 shell(可以把这个子进程成为 shell 进程),并通过 shell 执行参数command 所指定的命令;

system()的主要优点在于使用上方便简单,编程时无需自己处理对 fork()、exec 函数、waitpid()以及 exit()等调用细节,system()内部会代为处理;当然这些优点通常是以牺牲效率为代价的,使用 system()运行 shell命令需要至少创建两个进程,一个进程用于运行 shell、另外一个或多个进程则用于运行参数 command 中解析出来的命令,每一个命令都会调用一次 exec 函数来执行;所以从这里可以看出,使用 system()函数其效率会大打折扣,如果我们的程序对效率或速度有所要求,那么建议大家不是直接使用 system()。

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
	 int ret;
	 if (2 > argc)
	 exit(-1);
	 ret = system(argv[1]);
	 if (-1 == ret)
	 fputs("system error.\n", stderr);
	 else 
	 {
		 if (WIFEXITED(ret) && (127 == WEXITSTATUS(ret)))
		 fputs("could not invoke shell.\n", stderr);
	 }
	 exit(0);
}

在这里插入图片描述

本文参考正点原子的嵌入式LinuxC应用编程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/168338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

W13Scan 漏洞扫描器之XSS插件模块编写示例

一、背景 上周将W13Scan目录结构整理了一番&#xff0c;觉得要深入研究还得从代码层&#xff0c;于是尝试编写一下插件&#xff1b;框架本身已经集成了XSS扫描插件&#xff1b; 本篇文章的XSS插件的编写单纯是为了学习这个框架&#xff0c;所以只支持GET型&#xff0c;了解插…

一文解析企业网盘 带你寻找数据协作的“满分答案”

数据量急剧增长&#xff0c;线上办公逐渐成为常态。许多企业都会选择部署企业网盘来满足日益增长的数据管理与数据协作的需求。网盘市场乱花渐欲迷人眼&#xff0c;企业又该如何从中甄别最适合自己的企业网盘&#xff1f; 网盘&#xff0c;企业的数字基建 飞速发展的科技让企业…

[GIT] GIT拆分仓库--不丢git提交历史记录

背景 如果你的代码仓库里有多个目录&#xff0c;你想把其中一个目录拆分出去变成一个独立的代码仓库。重要的一点是拆分的过程中要保留git提交历史记录。 拆分步骤 1. 检查一下你的 repo 的根目录中。 进入根目录后&#xff0c;快速运行 ls 终端命令以确保列出所有子目录。…

ruoyi-vue版本框架(二)源码目录结构的讲解,与底层子项目的讲解

目录1 目录介绍2 ruoyi-common 子项目3 ruoyi-system 子项目4 配置文件5 ruoyi-framework6 数据库表7 druid 监控1 目录介绍 下载下来源码&#xff0c;后端一共有6个模块 其中 rouyi-admin这个子项目是整个若依框架的web项目&#xff0c;也就是我们要启动的后台就是这个子项目…

Django 第五章RESTFramework(DRF)框架初探以及认识serializers序列化器的增删改查

定义 Django REST framework (简称 DRF) 是一个强大灵活的wb api工具 功能完善&#xff0c;可快速开发api平台 官网地址 https://www.django-rest-framework.org/安装要求 pip3 install django3.2 pip3 install djangorestframework1.0 使用drf实现用户的增删改查 1.创建ap…

influxdb问题: unable to create database ‘mydb‘ 与 failed to send metrics to influx

[influx-metrics-publisher] ERROR i.m.i.InfluxMeterRegistry - [createDatabaseIfNecessary,117] - unable to create database mydb&#xff1b; [influx-metrics-publisher] ERROR i.m.i.InfluxMeterRegistry - [publish,161] - failed to send metrics to influx现象解决方…

Spring REST风格

REST&#xff08;Representational State Transfer&#xff09;&#xff0c;表现形式状态转换,它是一种软件架构风格。 当我们想要表示一个网络资源时&#xff0c;传统方式通常是用一个请求url表示一个操作。这样既不方便&#xff0c;也不安全&#xff0c;因为操作对于用户是透…

精要速览 | PacBio三代全长扩增子测序的多方向研究应用进展

随着测序技术的不断发展&#xff0c;组学研究愈加深入。在微生态研究领域&#xff0c;受限于二代测序读长的扩增子测序技术&#xff0c;迎来了“解放式”全面发展优化的三代测序时代——PacBio全长扩增子测序的优势逐渐显现&#xff1a;凭借其超长读长&#xff0c;高准确率&…

独立产品灵感周刊 DecoHack #044 - 新的一年如何管理你的时间

本周刊记录有趣好玩的独立产品设计开发相关内容&#xff0c;每周发布&#xff0c;往期内容同样精彩&#xff0c;感兴趣的伙伴可以点击订阅我的周刊。为保证每期都能收到&#xff0c;建议邮件订阅。欢迎通过 Twitter 私信推荐或投稿。本周看到了很多时间管理的工具类型产品&…

力扣中SQL刷题

小知识点 取余数&#xff1a;mod(数,2) 取第一个字母&#xff1a;left(name,1) 或者name like ‘M%’ 196. 删除重复的电子邮箱 题型&#xff1a;删除列A中重复的记录&#xff0c;指保留列B最小的那一行记录 答案&#xff1a;delete t1 from 表名 t1,表名 t2 where t1.列At2…

测试——自动化测试(Selenium工具)

目录 一、自动化测试的概念以及分类 二、Selenium—web自动化测试工具 1、自动化测试的一些前置工作 2、第一个自动化实例 3、总结​编辑 三、 Selenium常用方法 定位元素的方法 元素的操作 等待 强制等待&#xff08;待补充&#xff09; 隐式等待&#xff08;待补充&…

【python】python绘制相关性热力图

1.介绍 热力图&#xff08;heatmap&#xff09;&#xff0c;又称相关系数图&#xff0c;根据热力图中不同方块颜色对应的相关系数的大小&#xff0c;可以判断出变量之间相关性的大小。热力图通过对色块着色来显示数据的统计图表。绘图时&#xff0c;需指定颜色映射的规则。例如…

从运营小程序到实现数字化生态闭环还差几步?

近年来&#xff0c;不少企业纷纷开始关注小程序的开发。对于用户来说&#xff0c;小程序最大的好处就是能够即点即用&#xff0c;体验便捷。不知你是否发现&#xff0c;小程序经济已经开始制约中小企业的服务与合作。 对于一般的中小企业乃至大企业里没有什么IT预算的部门&…

缺省参数!C++教你如何成为一名优秀的舔狗

&#x1f451;专栏内容&#xff1a;C学习笔记⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;日拱一卒&#xff0c;功不唐捐 目录一、前言二、缺省参数1、缺省参数的定义2、缺省参数的分类Ⅰ、全缺省参数Ⅱ、半缺省参数三、注意事项1、直给中间的参数传参…

Docker中的容器命令

1、新建启动容器 docker run 镜像名 参数&#xff1a; -it 启动前台一个交互的终端 --name 启动容器后&#xff0c;赋予容器一个名字 -d: 后台运行容器&#xff0c;并返回容器ID&#xff0c;也即启动守护式容器&#xff1b; -P: 随机端口映射&#xff1b; -p: 指定端口映…

Svelte框架实现表格协同文档

首先&#xff0c;从框架搭建上,本篇示例采用当下流行的前后端分离的开发方式&#xff0c;前端使用npm作为脚手架搭建Svelte框架。 后端使用Java的SpringBoot作为后端框架。 首先&#xff0c;介绍下在前端Svelte框架下搭建在线表格编辑器。 1、在pageage.json文件中引入相关资源…

ANR问题分析

ANR概念 anr是指应用程序无响应&#xff0c;Android系统对于一些事件需要在一定时间范围内完成&#xff0c;如果超过预定时间未能得到有效响应或者响应时间过长&#xff0c;都会造成anr。通常发生anr时&#xff0c;系统会弹出一个提示框&#xff0c;让用户知道&#xff0c;该程…

AppScan介绍和安装

第一节-AppScan介绍和安装 1.简介 Appscan 10中文版是是全新网络安全漏洞扫描软件&#xff0c;软件可以直接可以对OS命令、SSRF和XXE攻击等漏洞进行检测&#xff0c;使得漏洞检测更加容易&#xff0c;提高漏洞的扫描效率。软件同时支持动态、静态、互动分析三种不同的测试功能…

OVN实验----NAT

概述 在L2互通、L3互通实验基础上通过NAT实现访问公网。 架构图如下&#xff0c; 这里两台逻辑路由器LR1和GLR是通过一台逻辑交换机LSjoin互连的&#xff0c; GLR和物理网络设备通过LSlocal相连。 物理拓扑 如上一个实验OVN实验----L2互通 逻辑拓扑 配置 开始实验前先检查…

设计模式之装饰者模式

装饰者模式 定义 先上定义&#xff1a;指在不改变现有对象结构的情况下&#xff0c;动态地给该对象增加一些职责&#xff08;即增加其额外功能&#xff09;的模式。 优缺点 优点&#xff1a; 1&#xff0c;装饰器是继承的有力补充&#xff0c;比继承灵活&#xff0c;在不改…