八_进程控制7+1 - exec函数族 必加fflush,搭配fork+execl+wait

实验1:execl 使用,必加 fflush()刷新流
实验2:fork() execl() wait() 联合使用

few:fork + exec + wait

引子:
运行如下程序,执行 ps axf 命令查看 得到如下 父子进程关系:

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>

#define LEFT 200
#define RIGHT 250

int main(void)
{
	int i,j,mark;
	pid_t pid;
	
	for(i = LEFT; i <= RIGHT; i++)
	{
		pid = fork();
		if(pid < 0)
		{
			fprintf(stderr,"fork() failed!\n");
			exit(1);
		}
		else if(pid == 0)//child
		{
			mark = 1;
			for(j = 2; j < i/2; j++)
			{
				if(i % j ==0)
					{
						mark = 0;
						break;
					}
			
			}

			if(mark)
				printf("%d is a primer\n",i);

			exit(0);//!!!
		}
		
	}

	sleep(1000);
	exit(0);
}


  2837 pts/0    Ss     0:01          |   \_ /bin/bash
 26731 pts/0    S+     0:00          |   |   \_ ./a.out
 26732 pts/0    Z+     0:00          |   |       \_ [a.out] <defunct>
 26733 pts/0    Z+     0:00          |   |       \_ [a.out] <defunct>
 26734 pts/0    Z+     0:00          |   |       \_ [a.out] <defunct>
 26735 pts/0    Z+     0:00          |   |       \_ [a.out] <defunct>
....

可以看到
shell 进程是 a.out 进程的父进程
a.out 父进程是 其余201个a.out进程的父进程

问题1: 关于shell进程 和 a.out进程之间的关系

那么问题来了,a.out 和 他fork出来的201个 a.out之间的关系很好理解。但是 shell进程 与 a.out父进程之间的关系 怎么理解呢?
我们之前了解到,父进程与子进程之间的关系 是通过复制父进程本身来得到子进程 ,fork() 之后 子进程和父进程代码和执行的位置都是一模一样,只不过父子进程再向下执行的时候 就会根据自己的身份 来选择 预先设定好的 自己需要执行的代码。那么 为什么 shell 进程 和 a.out进程是不一样的,按理说 shell 进程 的子进程应该也是 shell进程,现在 a.out 很明显是和 shell 不一样的。此处该怎么理解呢?


exec函数族:

NAME
execl, execlp, execle, execv, execvp, execvpe - execute a file 执行一个文件!!!

SYNOPSIS
   #include <unistd.h>

   extern char **environ;
   
//以NULL 结尾,代表当前传参结束
   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[]);

The exec() family of functions replaces the current process image with a new process image.

exec 函数族用一个新的进程映像替换当前的进程映像
在这里插入图片描述
并没有创建新进程,只是替换一个进程。

RETURN VALUE
The exec() functions return only if an error has occurred. The return value is -1, and errno is set to indicate the error.

exec只有执行失败的时候才有返回值。


实验1:exec 初体验, 与缓冲区之间的联系 一定注意 fflush()

命令:date +%s :指定格式获取当前时间

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>


int main(void)
{
	int i,j,mark,n;
	pid_t pid;
	
	puts("Begin!");//puts() 输出时会自动换行
	
	//以NULL 结尾,代表当前传参结束
	execl("/bin/date","date","+%s",NULL);
	perror("execl");//如果执行到此 说明 exec替换失败
	exit(1);

	puts("End!\n");
		
	
	exit(0);
}


mhr@ubuntu:~/Desktop/xitongbiancheng/test$ gcc test.c 
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ ./a.out 
Begin!
1613136072
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ 
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ 
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ 
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ ./a.out > /tmp/out
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ cat /tmp/out
1613136191
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ 

结果和预料一样,程序执行到 exec()的时候,当前进程被替换成了 date进程

注意:在命令行执行可执行程序时候,打印了 Begin, 而重定位到 /tmp/out后 则不会打印Begin。

why???

在命令行执行的时候 puts是输出到终端,即标准输出,是行缓冲,故有Begin 打印。而重定位到 /tmp/out 则 puts 是输出到 全缓冲文件,换行符没有刷新缓冲区的作用,只是存储到缓冲区,而还没来得及刷新 就执行了 execl() ,即 date进程映像替换了当前进程映像,缓冲区信息也被覆盖了。所以 一定要注意 :

fork()之前 要 fflush(NULL)
execl() 之前 要 fflush(NULL)
刷新缓冲区!!!

即:

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>


int main(void)
{
	int i,j,mark,n;
	pid_t pid;
	
	puts("Begin!");//puts() 输出时会自动换行
fflush(NULL);
	execl("/bin/date","date","+%s",NULL);
	perror("execl");//如果执行到此 说明 exec替换失败
	exit(1);

	puts("End!\n");
		
	
	exit(0);
}



mhr@ubuntu:~/Desktop/xitongbiancheng/test$ gcc test.c 
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ ./a.out > /tmp/out
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ 
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ cat /tmp/out
Begin!
1613136824
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ 

实验1中 用一个新的进程映像 替换 自己费心费力写出来的当前进程映像 没有任何意义,那还不如直接就用新的进程,所以 exec 主要应用在哪里呢??

实验2:exec的意义,fork() exec() wait() 三板斧使用:铺天盖地

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>


int main(void)
{
	int i,j,mark,n;
	pid_t pid;
	
	puts("Begin!");

	fflush(NULL);
	pid = fork();
	if(pid < 0)
	{
		perror("fork()");
		exit(1);
	}
	else if(pid == 0) //child  用子进程替换 date
	{
		fflush(NULL);
		execl("/bin/date","date","+%s",NULL);
		perror("execl");
		exit(1);
	}

	wait(NULL);

	puts("End!");
		
	
	exit(0);
}


mhr@ubuntu:~/Desktop/xitongbiancheng/test$ gcc test.c 
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ ./a.out 
Begin!
1613137594
End!
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ ./a.out > /tmp/out
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ cat /tmp/out
Begin!
1613137607
End!
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ 

父进程 负责创建 子进程。子进程负责干活,即替换为 date 进程,最后父进程等着释放子进程资源。

所以这里就解释了 问题1的问题
关于shell进程 和 a.out进程之间的关系。很明显 shell进程中 的一个子进程映像 替换成了 a.out 进程映像!!!

任务描述 Linux 中,init进程(初始化进程)是所有其他进程的父进程,那么是不是说就所有的进程都执行与init进程相同的功能了呢? 答案当然不是,Linux 中某些子进程和父进程的执行并不是完全相同的。他们是如何做到的呢? 下面我们就一起来学习进程载。 相关知识 为了完成本关任务,你需要掌握如何载非父进程进程exec函数族 Linuxexec函数族,它是若干函数的集合。exec 函数族的作用是根据指定的文件名或目录名找到可执行文件,并用它来取代调用进程的内容。换句话说,其功能是让子进程具有和父进程完全不同的新功能。 exec本身并非一个函数,是指一组函数,一共有6种在进程中启动另一个程序的方法: int execl(const char *path, const char *arg, ...); int execv(const char *path, 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[]); int execlp(const char *file, const char *arg, ...); int execvp(const char *file, char *const argv[]); exec函数族的6个函数看起来十分复杂,实际上无论是作用还是用法都十分相似,他们的命名规则: l和v表示参数是以列表还是以数组的方式提供,且都须以NULL结尾; **p**代表在path环境变量中搜索file文件; e表示该函数取envp[]数组,而不使用当前环境,即为程序提供新环境变量,一般很少使用。 进程调用一种 exec 函数时,该进程完全由新程序替换,而新程序则从其 main 函数开始执行。exec只是用磁盘上的一个新程序替换了当前进程的正文段,数据段,堆段和栈段。并没有创建新进程,所以进程的 ID 是不变的。 注意: 一旦exec函数执行成功,它就不会返回了,进程结束。但是如果exec函数执行失败, 它会返回失败的信息,并且进程继续执行后面的代码!执行失败的话,须用exit()函数来让子进程退出!(exit函数调用需导入stdlib.h库函数) 进程载 我们使用execl()函数来做示例: int execl(const char *path, const char *arg, ...) 函数参数说明: path:要执行的程序路径。可以是绝对路径或者是相对路径。在execv、execve、execlexecle这4个函数中,使用带路径名的文件名作为参数。 arg:程序的第0个参数,即程序名自身。相当于argv[O]。 …:命令行参数列表。调用相应程序时有多少命令行参数,就需要有多少个输入参数项。最后应该以NULL结尾,表明命令行参数结束。 返回值:-1表明调用exec失败,无返回表明调用成功。 #include <unistd.h> int main() { printf("before exec\n"); execl("/bin/ls", "ls", "-a", "-l", "-h", NULL); //若 execl() 执行成功,下面则不执行,因为当前进程已经被执行的 ls 替换了 printf("after exec\n"); return 0; } 执行语句说明: /bin/ls:外部程序,这里是/bin目录的ls可执行程序,须带上路径(相对或绝对) ls:没有意义,如果需要给这个外部程序传参,这里须要写上字符串,至于字符串内容任意 -a,-l,-h:给外部程序ls传的参数 NULL:代表给外部程序 ls 传参结束 执行结果: 编程要求 在右侧编辑器补充代码,要求如下: 创建进程; 在父进程中输出entering main process---; 在子进程中使用execl()函数调用src/step2/hello.c文件,测试环境中已将path置为src/step2,因此带路径的文件名为./hello 测试说明 平台会对你编写的代码进行测试: 预期输出: entering main process--- Hello exec! This is another task。
最新发布
11-27
<think>好的,我现在需要处理用户提供的这个汇编指令:“addi a5,a5,513 #1201 <_fflush_r+0x17>”。首先,我要确认自己对这个指令的理解是否正确。让我分步骤来分析和解释这个指令的各个部分,以及它在程序中的作用。 首先,用户给出的指令是RISC-V架构的汇编语言,因为指令中的助记符是“addi”,这是RISC-V中的立即数法指令。接下来,我需要分解指令的各个部分: 1. **指令操作码(Opcode)**:addi,这表示立即数法。在RISC-V中,addi指令的作用是将寄存器中的值与一个立即数相,结果存储在目标寄存器中。格式通常是addi rd, rs1, imm,其中rd是目标寄存器,rs1是源寄存器,imm是12位的立即数。 2. **操作数分析**:这里的操作数是a5, a5, 513。根据addi的语法,目标寄存器rd和源寄存器rs1都是a5,立即数是513。所以这条指令的作用是将寄存器a5的当前值上513,并将结果存回a5中。这相当于执行a5 = a5 + 513。 3. **立即数的范围检查**:RISC-V的addi指令使用12位有符号立即数,范围是-2048到2047。这里的立即数是513,显然在合法范围内,没有问题。 4. **注释部分**:后面的注释是“#1201 <_fflush_r+0x17>”。这部分通常由反汇编工具生成,包含地址和符号信息。其中: -1201”可能是这条指令在程序中的地址,比如0x1201- “<_fflush_r+0x17>”表示这条指令位于函数_fflush_r的偏移0x17处。这意味着在_fflush_r函数内部,从函数入口点开始偏移0x17字节的位置,存放着这条指令。 接下来,我需要思考这条指令在程序中的可能作用。由于涉及_fflush_r函数,这是标准库中处理缓冲区的函数,通常用于刷新输出缓冲区。addi指令在这里可能是调整某个指针或偏移量,比如调整缓冲区的位置或者计算某个结构体的成员地址。例如,在结构体中,a5可能指向某个成员的基地址,上513的偏移量后,访问另一个成员或调整指针位置。 但需要注意,单纯从这一条指令无法完全确定上下文,可能需要查看周围的指令。例如,a5在之前可能被载了某个基地址,这里的法用于计算特定的偏移。另外,513这个数值是否有特殊含义?比如,是否是缓冲区的大小,或者某种对齐后的值? 另外,检查是否存在可能的错误,比如立即数是否被正确编码。513的二进制是0b1000000001,作为12位有符号数,是合法的,因此不会有问题。 总结来说,这条指令执行的是将a5寄存器增513,用于程序中的某个地址计算或数值调整,具体用途需要结合上下文进一步分析。</think>让我们逐步分析该RISC-V汇编指令: **指令结构分解**: $$addi\ a5,a5,513\ \ \ \#1201\ <\_fflush\_r+0x17>$$ 1. **指令类型**: - `addi`:立即数法指令(Add Immediate) - RISC-V基础指令集(RV32I/I64)的标准算术指令 2. **操作数解析**: - 目标寄存器:`a5`(RISC-V通用寄存器x15) - 源寄存器:`a5`(使用寄存器自身值) - 立即数:`513`(十进制) 3. **执行效果**: $$ a5 \leftarrow a5 + 513 $$ 将当前寄存器a5的值增513后写回a5 4. **技术限制**: - 立即数范围:12位有符号数(-2048~2047- 513(0x201)在合法范围内 5. **注释解析**: - `#1201`:指令地址(可能是0x1201- `<\_fflush\_r+0x17>`:位于标准库函数`_fflush_r`的+0x17偏移处 **典型应用场景**: 1. **地址计算**:在缓冲区操作中调整指针位置 2. **结构体偏移**:访问特定结构成员(如513字节的偏移) 3. **循环计数器**:在底层驱动中调整硬件寄存器值 4. **缓冲区扩容**:调整I/O缓冲区大小参数 **调试建议**: 1. 检查`a5`前值:使用`gdb`查看指令执行前的寄存器值 2. 查看内存映射:`info proc mappings`确认操作区域属性 3. 反汇编上下文:`disas _fflush_r`查看前后指令序列 4. 验证立即数:确认513是否符合内存对齐要求(如非对齐访问可能触发异常) **示例上下文分析**: 假设在`_fflush_r`函数中: ``` 1200: 83c58593 addi a1,a5,-4 # 前一条指令 1204: 20178793 addi a5,a5,513 # 当前指令 1208: 8d5ff0ef jal ra,0xcdc # 调用子函数 ``` 可能用于调整文件流结构体指针(`FILE*`),其中513可能对应特定字段的偏移量。 **注意**:具体作用需结合完整函数逻辑和系统ABI分析,单条指令的精确解释需要上下文信息支持。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ma浩然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值