进程创建
fork函数初识
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);
//返回值:自进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
![![[Pasted image 20260213162221.png]]](https://i-blog.csdnimg.cn/direct/04103f8b7ec74aab94daa62b99d3a51c.png)
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序
int main( void )
{
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
运行结果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0
这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after消息有43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
fork函数返回值
- 子进程返回0,
- 父进程返回的是子进程的pid。
写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:
![![[Pasted image 20260213162731.png]]](https://i-blog.csdnimg.cn/direct/ac4b2952a70c4df28ed17baeda20ec51.png)
![![[Pasted image 20260213181552.png]]](https://i-blog.csdnimg.cn/direct/fda0ea1c88574c4eac91ad6bc0bfc798.png)
运行时出问题
字符串常量是具有常性的,在字符常量区不能对对应的常量数据进行修改
hello Linux保存的是字符串的起始地址,这个地址是虚拟地址,指针解引用进行修改写入时,一定要进行虚拟到物理地址的转化,页表中映射的条目是只读位,页表不让映射,所以写入失败
操作系统层面上不让进行修改,和语言没关系
fork常规用法
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
fork调用失败的原因
- 系统中有太多的进程
- 实际用户的进程数超过了限制
进程终止
main函数的返回值就是进程的退出码,用来表明当前进程的执行情况
0表示成功,非0表示失败
其他函数退出,仅仅表示函数调用完毕
echo $?
bash内部会记录最近一次进程执行完毕时的退出码
![![[Pasted image 20260214043653.png]]](https://i-blog.csdnimg.cn/direct/2fc721467df844319fb6edc442034b3a.png)
![![[Pasted image 20260214045907.png]]](https://i-blog.csdnimg.cn/direct/6ad7920052f84ca5abd2f8fae9896e2e.png)
![![[Pasted image 20260214050048.png]]](https://i-blog.csdnimg.cn/direct/7ad0eb42c1434b5791d766a430ecb5c7.png)
![![[Pasted image 20260214050524.png]]](https://i-blog.csdnimg.cn/direct/5d9ad16946f34f9fb7bacfa94a3b8c9e.png)
自定义
![![[Pasted image 20260214052034.png]]](https://i-blog.csdnimg.cn/direct/c42b6f2e28e845038fde294133662088.png)
![![[Pasted image 20260214052344.png]]](https://i-blog.csdnimg.cn/direct/8c2dda076b5e420697195756943e68a2.png)
![![[Pasted image 20260214052359.png]]](https://i-blog.csdnimg.cn/direct/7b1b9f3f0f914243a71a3558ff89200d.png)
![![[Pasted image 20260214134545.png]]](https://i-blog.csdnimg.cn/direct/742accedd9d5494282474aeccb6273af.png)
![![[Pasted image 20260214134555.png]]](https://i-blog.csdnimg.cn/direct/ee370f4a579d42c3b3bdf7b13408be00.png)
其他函数退出,仅仅表示函数调用完毕,后面的printf也执行了
errno:除了进程退出,函数退出,通过函数的返回值知道函数的执行情况
调用函数通常看到两种结果
- 函数的执行结果–FILE*,NULL
- 函数的执行情况–成功、失败、什么原因
![![[Pasted image 20260214135503.png]]](https://i-blog.csdnimg.cn/direct/a2c332e037204a7385d72a36dca33863.png)
![![[Pasted image 20260214135544.png]]](https://i-blog.csdnimg.cn/direct/088a6ddd06b64583a83091df36153c9b.png)
函数有错误码
![![[Pasted image 20260214140128.png]]](https://i-blog.csdnimg.cn/direct/2c1baff064ab44e5a5d5377b011c6053.png)
![![[Pasted image 20260214140144.png]]](https://i-blog.csdnimg.cn/direct/e23fccd1ba124ce3b053b91b09d149e6.png)
进程退出场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
进程出现异常的本质是进程收到了异常信号,每个信号都有不同的编号,不同的信号编号表示不同的异常的原因
![![[Pasted image 20260214175101.png]]](https://i-blog.csdnimg.cn/direct/749eaaaa537c49e9b7214aa93f43e206.png)
![![[Pasted image 20260214180056.png]]](https://i-blog.csdnimg.cn/direct/64b8dc8df78649628d6c07bc624c4fe5.png)
![![[Pasted image 20260214180214.png]]](https://i-blog.csdnimg.cn/direct/1164a5e00b02425d9ff29dafd090c735.png)
![![[Pasted image 20260214181855.png]]](https://i-blog.csdnimg.cn/direct/fba1b512b4c54be0b794b455af8ba728.png)
![![[Pasted image 20260214182431.png]]](https://i-blog.csdnimg.cn/direct/044b55188b604ab786dc8e3ed9a81e5b.png)
![![[Pasted image 20260214182459.png]]](https://i-blog.csdnimg.cn/direct/5b1b207bab4849bb8debd16a414dd7c0.png)
![![[Pasted image 20260214182712.png]]](https://i-blog.csdnimg.cn/direct/6b0810170c204de8af114f9d8020052e.png)
![![[Pasted image 20260214182745.png]]](https://i-blog.csdnimg.cn/direct/1772a426c02843c9a2f388f97f5d3601.png)
任何进程最终的执行情况,可以使用两个数字表明具体执行的情况
- 信号编号signumber
- 进程退出码exit_code
进程常见退出方法
正常终止(可以通过 echo $? 查看进程退出码):
- 从main返回
- 调用exit
![![[Pasted image 20260215011217.png]]](https://i-blog.csdnimg.cn/direct/b17661c85e63411ba839c4e4338dd510.png)
![![[Pasted image 20260215011308.png]]](https://i-blog.csdnimg.cn/direct/8182fdfa6aa74b50bf86476634914781.png)
![![[Pasted image 20260215011406.png]]](https://i-blog.csdnimg.cn/direct/592a6d745dad439f96dce9ee3ebbda55.png)
exit就是用来终止进程的,exit(退出码)
在进程代码中,任意地方调用exit,都表示进程退出
3. _exit
异常退出:
- ctrl + c,信号终止
_exit函数
#include <unistd.h>
void _exit(int status);
//参数:status 定义了进程的终止状态,父进程通过wait来获取该值
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。
exit函数
#include <unistd.h>
void exit(int status);
exit最后也会调用exit, 但在调用exit之前,还做了其他工作:
- 执行用户通过 atexit或on_exit定义的清理函数。
- 关闭所有打开的流,所有的缓存数据均被写入
- 调用_exit
![![[Pasted image 20260213163219.png]]](https://i-blog.csdnimg.cn/direct/a26d563dc4f84b0393a9cd1698b26f26.png)
int main()
{
printf("hello");
exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
hello[root@localhost linux]#
int main()
{
printf("hello");
_exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
[root@localhost linux]#
exit支持刷新缓冲区,_eixt不支持
return退出
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。
进程等待
进程等待必要性
- 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
- 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
- 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
进程等待的方法
wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
![![[Pasted image 20260215094333.png]]](https://i-blog.csdnimg.cn/direct/74029a2d5f654fa589bd3c3509a14260.png)
![![[Pasted image 20260215093934.png]]](https://i-blog.csdnimg.cn/direct/92c30869c2a946b1b80b4d4e3f905e73.png)
![![[Pasted image 20260215100359.png]]](https://i-blog.csdnimg.cn/direct/cc72c28ded3a4f4699741be9e7be270d.png)
![![[Pasted image 20260215095844.png]]](https://i-blog.csdnimg.cn/direct/c9593c47ad1c435fad524f5b7f81fc32.png)
![![[Pasted image 20260215100509.png]]](https://i-blog.csdnimg.cn/direct/896d776553ac4ed2bba8081a9beb10bd.png)
waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
![![[Pasted image 20260215103656.png]]](https://i-blog.csdnimg.cn/direct/52f586a39f384b68b021bf23ac9a9434.png)
![![[Pasted image 20260215104525.png]]](https://i-blog.csdnimg.cn/direct/4894dcc62754465880316f8a5b2b4291.png)
- 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
- 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
- 如果不存在该子进程,则立即出错返回。
![![[Pasted image 20260213164613.png]]](https://i-blog.csdnimg.cn/direct/c61e0d81292d46d3a4b23cfe38baea6b.png)
获取子进程status
- wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递NULL,表示不关心子进程的退出状态信息。
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
- status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
status在函数中是整数,返回的是两方面的信息,退出码和退出信号,这个整数是局部用类似位图的方式进行使用,status有自己的格式
![![[Pasted image 20260213164806.png]]](https://i-blog.csdnimg.cn/direct/658c5af6be87437e8f106d4184422c87.png)
测试代码:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void )
{
pid_t pid;
if ( (pid=fork()) == -1 )
perror("fork"),exit(1);
if ( pid == 0 ){
sleep(20);
exit(10);
} else {
int st;
int ret = wait(&st);
if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出
printf("child exit code:%d\n", (st>>8)&0XFF);
} else if( ret > 0 ) { // 异常退出
printf("sig code : %d\n", st&0X7F );
}
}
}
测试结果:
[root@localhost linux]# ./a.out #等20秒退出
child exit code:10
[root@localhost linux]# ./a.out #在其他终端kill掉
sig code : 9
![![[Pasted image 20260215133644.png]]](https://i-blog.csdnimg.cn/direct/8c8ed68e958749f9b2ab75e7354fe55f.png)
![![[Pasted image 20260215133751.png]]](https://i-blog.csdnimg.cn/direct/6f957110c9c448a7b5f3828f0ae55541.png)
![![[Pasted image 20260215134203.png]]](https://i-blog.csdnimg.cn/direct/14c2b238dd534efd9599f767d7c9546d.png)
![![[Pasted image 20260215134147.png]]](https://i-blog.csdnimg.cn/direct/209d3ee87402462f9e16721d728f9e3e.png)
加入除0错误
![![[Pasted image 20260215140244.png]]](https://i-blog.csdnimg.cn/direct/b611a556671b461fa0a8acbeb1708fd9.png)
![![[Pasted image 20260215140315.png]]](https://i-blog.csdnimg.cn/direct/5ccd874e77e945bc83c91154c31a05fd.png)
添加野指针
![![[Pasted image 20260215140820.png]]](https://i-blog.csdnimg.cn/direct/11fec3af041442cdbf52c87d8e4efae4.png)
![![[Pasted image 20260215140922.png]]](https://i-blog.csdnimg.cn/direct/a1eaef3968d645f985ad6620b351ba47.png)
通过宏查看status
![![[Pasted image 20260215143438.png]]](https://i-blog.csdnimg.cn/direct/c787af87ed224c10b600414fc9a8ed77.png)
![![[Pasted image 20260215144335.png]]](https://i-blog.csdnimg.cn/direct/25cfb59a7f69470fbfe1211ec7fcad6c.png)
具体代码实现
- 进程的阻塞等待方式:
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("%s fork error\n",__FUNCTION__);
return 1;
} else if( pid == 0 ){ //child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(257);
} else{
int status = 0;
pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
printf("this is test for wait\n");
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
}else{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
运行结果:
[root@localhost linux]# ./a.out
child is run, pid is : 45110
this is test for wait
wait child 5s success, child return code is :1.
![![[Pasted image 20260215133644.png]]](https://i-blog.csdnimg.cn/direct/a6d2e73441ad4477b138151070e19145.png)
- 进程的非阻塞等待方式:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("%s fork error\n",__FUNCTION__);
return 1;
}else if( pid == 0 ){ //child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(1);
} else{
int status = 0;
pid_t ret = 0;
do
{
ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
if( ret == 0 ){
printf("child is running\n");
}
sleep(1);
}while(ret == 0);
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
}else{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
![![[Pasted image 20260215154424.png]]](https://i-blog.csdnimg.cn/direct/793c7f3abc4241efb28d0123cb480e79.png)
![![[Pasted image 20260215154739.png]]](https://i-blog.csdnimg.cn/direct/dbfbdd5a8f9f4243af0ca09ac596255c.png)
例子
![![[Pasted image 20260215160234.png]]](https://i-blog.csdnimg.cn/direct/6ccf1a3b1b6f4beb880ba6c59c25f951.png)
![![[Pasted image 20260215160402.png]]](https://i-blog.csdnimg.cn/direct/0b9a19ebe406485abd3a77524c8f9414.png)
![![[Pasted image 20260215160649.png]]](https://i-blog.csdnimg.cn/direct/58513e68921243508290349a29aec438.png)
![![[Pasted image 20260215160823.png]]](https://i-blog.csdnimg.cn/direct/f0ba6322464941f99e8eaf699c731873.png)
进程程序替换
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
![![[Pasted image 20260213170635.png]]](https://i-blog.csdnimg.cn/direct/b5c36071280e4705a37f2e0c09b90a5f.png)
替换函数
其实有六种以exec开头的函数,统称exec函数:
#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
函数解释
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值。
命名理解
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
| 函数名 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
|---|---|---|---|
| execl | 列表 | 不是 | 是 |
| execlp | 列表 | 是 | 是 |
| execle | 列表 | 不是 | 不是,须自己组装环境变量 |
| execv | 数组 | 不是 | 是 |
| execvp | 数组 | 是 | 是 |
| execvp | 数组 | 不是 | 不是,须自己组装环境变量 |
| exec调用举例如下: |
#include <unistd.h>
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}
事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
下图exec函数族 一个完整的例子
![![[Pasted image 20260213171252.png]]](https://i-blog.csdnimg.cn/direct/d02b7cca7d614a69aa8eb92eb424d5de.png)
例子
![![[Pasted image 20260216105746.png]]](https://i-blog.csdnimg.cn/direct/c373d9ac5cf149b7a85c43d492ada920.png)
![![[Pasted image 20260216110141.png]]](https://i-blog.csdnimg.cn/direct/a825ae596c6b492bb48494d340d2f1e5.png)
![![[Pasted image 20260216110530.png]]](https://i-blog.csdnimg.cn/direct/4fd27158a4094ab6a73162d34c898e8b.png)
可以用自己的程序执行系统指令
![![[Pasted image 20260216112359.png]]](https://i-blog.csdnimg.cn/direct/319acf5d5a3c424abd33f83baa680857.png)
![![[Pasted image 20260216112554.png]]](https://i-blog.csdnimg.cn/direct/82c3443f53054d14b5072fb20a76f545.png)
![![[Pasted image 20260216113320.png]]](https://i-blog.csdnimg.cn/direct/9aabe233b0c841a18234aac2d6492aa4.png)
![![[Pasted image 20260216113444.png]]](https://i-blog.csdnimg.cn/direct/242539c094cf4e4fb9ba832120c98e38.png)
- 程序替换一旦成功,
exec*后续的代码不再执行,因为被替换掉了 exec*只有失败返回值,没有成功返回值- 替换完成,不创建新的程序
- 创建一个进程,先创建pcb,地址空间,页表等;再把程序加载到内存。程序替换的本质工作就是加载
- 标准传参,也可以非标准传参
- 多进程版本
![![[Pasted image 20260218000217.png]]](https://i-blog.csdnimg.cn/direct/1edd79f3fdac4e2e945c680494c2f5b2.png)
![![[Pasted image 20260218000532.png]]](https://i-blog.csdnimg.cn/direct/0b627d00bb6a4f5e84fd592aeb17936a.png)
因为子进程进行代码和数据替换时,因为进程的独立性,为了不影响父进程,进行写时拷贝
写时拷贝的本质就是开辟新的空间
execlp,p:PATH,只需要告诉程序的名字,系统替换的时候会自动去PATH环境变量去查找
![![[Pasted image 20260218091823.png]]](https://i-blog.csdnimg.cn/direct/8a25977f799c4ee4a1463f3a970e5be0.png)
![![[Pasted image 20260218091808.png]]](https://i-blog.csdnimg.cn/direct/c18cf0ca1b5e459aad73bbe4fc044f79.png)
execv,
![![[Pasted image 20260218092923.png]]](https://i-blog.csdnimg.cn/direct/760c22c40be249a9823eb66c2b958173.png)
execvp
![![[Pasted image 20260218093158.png]]](https://i-blog.csdnimg.cn/direct/231f4f41cde24b12aa47d8b536dc782e.png)
替换自己的程序,用process调mytest
![![[Pasted image 20260218093922.png]]](https://i-blog.csdnimg.cn/direct/76c77db04a264d1795b74255869de1a8.png)
![![[Pasted image 20260218094201.png]]](https://i-blog.csdnimg.cn/direct/8be0406daa684d2685278527d1bfb1f4.png)
makefile
.PHONY:all
all:myprocess mytest
mytest:mytest.cc
g++ -o $@ $^ -std=c++11
myprocess:myprocess.c
gcc -o $@ $^
.PHONY:clean
rm -f myprocess mytest
![![[Pasted image 20260218094613.png]]](https://i-blog.csdnimg.cn/direct/d741c201ff994c9da1c5c21de5bc50c2.png)
![![[Pasted image 20260218095255.png]]](https://i-blog.csdnimg.cn/direct/29b379049f55423d8980dc8101ad9246.png)
![![[Pasted image 20260218095435.png]]](https://i-blog.csdnimg.cn/direct/cb413cf0b4554a2ba5ebe05bb49a45f6.png)
![![[Pasted image 20260218095604.png]]](https://i-blog.csdnimg.cn/direct/a172161b02494cfcafc6db7b79cfd184.png)
调shell脚本
![![[Pasted image 20260218141451.png]]](https://i-blog.csdnimg.cn/direct/e83ac6722d324ff9b21a51aaf67eb8de.png)
![![[Pasted image 20260218143427.png]]](https://i-blog.csdnimg.cn/direct/caadead5b63e4797be2e9a665258c724.png)
![![[Pasted image 20260218143528.png]]](https://i-blog.csdnimg.cn/direct/a71139b6f94d4360acb759bf365de15d.png)
调python文件
![![[Pasted image 20260218143929.png]]](https://i-blog.csdnimg.cn/direct/6ad43f4bf2684f65b50ffa7227e214ba.png)
![![[Pasted image 20260218143952.png]]](https://i-blog.csdnimg.cn/direct/31c258b813e941ef8db307009d9d2b45.png)
所有的语言都是进程,在系统层面,只有代码和数据,只能看到进程
程序替换可以拿到所有的环境变量
![![[Pasted image 20260219100752.png]]](https://i-blog.csdnimg.cn/direct/984b824908704aacaea5bd5ea8360c83.png)
![![[Pasted image 20260219101109.png]]](https://i-blog.csdnimg.cn/direct/7b96c3341d5a4393bdc486cd472f5659.png)
![![[Pasted image 20260219101434.png]]](https://i-blog.csdnimg.cn/direct/fe9ad8d62fd9402f840c1d40d6934682.png)
结果同上
默认可以通过地址空间继承的方式,让所有的子进程拿到环境变量
进程程序替换,不会替换环境变量数据
- 如果想让子进程继承全部的环境变量,直接能拿到
- 新增环境变量
putenv
![![[Pasted image 20260219105453.png]]](https://i-blog.csdnimg.cn/direct/0a3be059ae034930bc87cc8a874aa02f.png)
putenv,导到父进程的命令行参数里
新增环境变量不会影响前者,会影响后者
3. 设置全新的环境变量给子进程
execle
![![[Pasted image 20260219111229.png]]](https://i-blog.csdnimg.cn/direct/47f84e2dcf394c59a3100a4d70148a71.png)
![![[Pasted image 20260219111254.png]]](https://i-blog.csdnimg.cn/direct/4dc3a920ce504a6b90dd315a36780cb2.png)
直接把父进程的环境变量表传给子进程
![![[Pasted image 20260219111609.png]]](https://i-blog.csdnimg.cn/direct/452b2d19134b4d958a30e0c0d455f084.png)
简易的shell
考虑下面这个与shell典型的互动:
[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
PID TTY TIME CMD
3451 pts/0 00:00:00 bash
3514 pts/0 00:00:00 ps
用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。
![![[Pasted image 20260213171622.png]]](https://i-blog.csdnimg.cn/direct/2fc1537a831a4950957e29fc119a7925.png)
然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
所以要写一个shell,需要循环以下过程:
- 获取命令行
- 解析命令行
- 建立一个子进程(fork)
- 替换子进程(execvp)
- 父进程等待子进程退出(wait)
根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了。
实现代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#define MAX_CMD 1024
char command[MAX_CMD];
int do_face()
{
memset(command, 0x00, MAX_CMD);
printf("minishell$ ");
fflush(stdout);
if (scanf("%[^\n]%*c", command) == 0) {
getchar();
return -1;
}
return 0;
}
char **do_parse(char *buff)
{
int argc = 0;
static char *argv[32];
char *ptr = buff;
while(*ptr != '\0') {
if (!isspace(*ptr)) {
argv[argc++] = ptr;
while((!isspace(*ptr)) && (*ptr) != '\0') {
ptr++;
}
}else {
while(isspace(*ptr)) {
*ptr = '\0';
ptr++;
}
}
}
argv[argc] = NULL;
return argv;
}
int do_exec(char *buff)
{
char **argv = {NULL};
int pid = fork();
if (pid == 0) {
argv = do_parse(buff);
if (argv[0] == NULL) {
exit(-1);
}
execvp(argv[0], argv);
}else {
waitpid(pid, NULL, 0);
}
return 0;
}
int main(int argc, char *argv[])
{
while(1) {
if (do_face() < 0)
continue;
do_exec(command);
}
return 0;
}
输出命令行提示符
![![[Pasted image 20260219133507.png]]](https://i-blog.csdnimg.cn/direct/d70f330f44b9443fbe0f94a5b3439be1.png)
![![[Pasted image 20260219134036.png]]](https://i-blog.csdnimg.cn/direct/0ddab2817ffd418b843f5662f679ea09.png)
![![[Pasted image 20260219134114.png]]](https://i-blog.csdnimg.cn/direct/0c77fa5081814b96a8b31a03697e954e.png)
接收用户输入
![![[Pasted image 20260219134754.png]]](https://i-blog.csdnimg.cn/direct/017013698bba451aa3ad2a92d859d48d.png)
![![[Pasted image 20260219134832.png]]](https://i-blog.csdnimg.cn/direct/aef9f0c485fb4fcdad251cbed291b541.png)
scanf默认只能通过空格分隔来获取字符串
fgets,通过指定的文件流获取一行的内容,把获取到的行内容放到缓冲区指针所指向的内容当中,获取成功就返回地址,获取失败就返回NULL
![![[Pasted image 20260219141742.png]]](https://i-blog.csdnimg.cn/direct/5d42a3b8d02b43ecb766b27d3d0be3e2.png)
![![[Pasted image 20260219141810.png]]](https://i-blog.csdnimg.cn/direct/4125adc62cae4388af2ef58ed58fc0b2.png)
输入的时候敲回车会输入换行,需要去掉换行符
fgets是c函数,默认会给接收的字符串末尾加上\0
![![[Pasted image 20260219152632.png]]](https://i-blog.csdnimg.cn/direct/25df112b48ee4d278c597dcca6cc611c.png)
![![[Pasted image 20260219152806.png]]](https://i-blog.csdnimg.cn/direct/2a53febb47294169a79deb67e10b1c9b.png)
封装
![![[Pasted image 20260219153023.png]]](https://i-blog.csdnimg.cn/direct/62f8afefa7274752b5f4d79520dd9323.png)
对输入的字符串进行切割
strtok根据分隔符,把指定的字符串分割成若干子串
形成的char*数组必须以NULL结尾,方便直接传参
![![[Pasted image 20260219154355.png]]](https://i-blog.csdnimg.cn/direct/fcf89fac479c41c6b2e92702effad9c9.png)
![![[Pasted image 20260219154429.png]]](https://i-blog.csdnimg.cn/direct/be80cab0b9664e88b79298a994b72cf6.png)
while循环条件用=是因为,当strtok切割失败之后,会返回NULL,正好再一次赋值,把argv数组的最后设置为NULL
然后while循环判断argv[i]为NULL,不符合条件,退出循环
封装
![![[Pasted image 20260219155448.png]]](https://i-blog.csdnimg.cn/direct/1dea461ede7a4357bd0f1ee2eee873c3.png)
![![[Pasted image 20260219155611.png]]](https://i-blog.csdnimg.cn/direct/100e936c67a642a4812d5d186db717de.png)
执行命令
![![[Pasted image 20260219165621.png]]](https://i-blog.csdnimg.cn/direct/e12d55c526754ec5bbced16b1daafc4f.png)
![![[Pasted image 20260219165739.png]]](https://i-blog.csdnimg.cn/direct/3ebc5c40040e488480febca5f88fe46d.png)
封装
![![[Pasted image 20260219170304.png]]](https://i-blog.csdnimg.cn/direct/af1453c591454178b565c576e7e9984e.png)
shell命令循环使用
![![[Pasted image 20260219170725.png]]](https://i-blog.csdnimg.cn/direct/1e390bd621704b9a8662198be142ccc5.png)
![![[Pasted image 20260219172652.png]]](https://i-blog.csdnimg.cn/direct/113629dcd5ad481bb112c787f1f76da3.png)
处理内建命令
![![[Pasted image 20260219174319.png]]](https://i-blog.csdnimg.cn/direct/22b61401c5f74a57abd5cc299edf02e8.png)
子进程在执行对应的命令,执行cd命令的是子进程,应该让父进程更改路径
![![[Pasted image 20260219174822.png]]](https://i-blog.csdnimg.cn/direct/7f171259e893417cafc43cfbe206e394.png)
![![[Pasted image 20260219175212.png]]](https://i-blog.csdnimg.cn/direct/6c36b287a96048b3b679aa695145e0a0.png)
![![[Pasted image 20260219175314.png]]](https://i-blog.csdnimg.cn/direct/7fa9bc24ec504af0a3fb22a58958651e.png)
![![[Pasted image 20260219175348.png]]](https://i-blog.csdnimg.cn/direct/313b6296a80340ecaeda3729d764b6af.png)
直接按回车有空串问题
![![[Pasted image 20260219182248.png]]](https://i-blog.csdnimg.cn/direct/7aaa052b75dc401f92e6690a7633455e.png)
![![[Pasted image 20260219182318.png]]](https://i-blog.csdnimg.cn/direct/90e2225f65634fe5a14303da72f0744e.png)
![![[Pasted image 20260219182330.png]]](https://i-blog.csdnimg.cn/direct/bbe8eca5275f4b9687be80aca744908e.png)
提示路径的变化
![![[Pasted image 20260219184031.png]]](https://i-blog.csdnimg.cn/direct/a85a7f1b7c1a4132b88419432e902bb5.png)
![![[Pasted image 20260219184211.png]]](https://i-blog.csdnimg.cn/direct/ce6377de48994770b1de759fb463731b.png)
…需要实现
![![[Pasted image 20260219184230.png]]](https://i-blog.csdnimg.cn/direct/8a54eb25870d4562a213221a53c5ce65.png)
使用getpwd接口
![![[Pasted image 20260219234807.png]]](https://i-blog.csdnimg.cn/direct/7bf34f74e0f345cb851a3c33c90cc3e5.png)
![![[Pasted image 20260219234824.png]]](https://i-blog.csdnimg.cn/direct/78a4ba6f1cc1493c84c5527c8e2a0ea7.png)
不是获取的…,而是获取的从根目录开始的绝对路径
![![[Pasted image 20260220000234.png]]](https://i-blog.csdnimg.cn/direct/13921ec393a341b5999f5327d896774c.png)
![![[Pasted image 20260220000305.png]]](https://i-blog.csdnimg.cn/direct/af2aeb1e78c84841b47ae4ff4b04bce1.png)
导入环境变量
子进程执行了export,没有影响到bash进程,需要导入到bash的上下文里
export命令也是内建命令
![![[Pasted image 20260220001116.png]]](https://i-blog.csdnimg.cn/direct/3acb4f30f4714da3ac71c9047f427599.png)
这样的新建的环境变量,第一次查询是有的,后续通过shell命令查询,又不见了
因为argv指向的是一个字符串,也就是在commandline里,每次循环使用shell,commandline字符串里的内容直接被重新覆盖了,所以导入的环境变量一直在被覆盖
![![[Pasted image 20260220080927.png]]](https://i-blog.csdnimg.cn/direct/1468900556364d088cb5f128eb33ebe3.png)
![![[Pasted image 20260220084055.png]]](https://i-blog.csdnimg.cn/direct/17dbbbf52ef14cec93788c68d44bea4f.png)
把要导入的环境变量argv拷贝到env里,全局定义的缓冲区里,直接再putenv
这样不会因为命令行进行重置,一直重新覆盖,进而影响环境变量
进程退出码
![![[Pasted image 20260220084707.png]]](https://i-blog.csdnimg.cn/direct/0d7f293856d940f4ab2b0bb03e2346b5.png)
![![[Pasted image 20260220084825.png]]](https://i-blog.csdnimg.cn/direct/b7e565284c934c07bcc1e0ff2db52fa8.png)
![![[Pasted image 20260220090220.png]]](https://i-blog.csdnimg.cn/direct/a874d459b66b45a99079d981db8703cf.png)
![![[Pasted image 20260220090434.png]]](https://i-blog.csdnimg.cn/direct/8b19e603ab7f46c0b88bccc654e77426.png)
ls带颜色
![![[Pasted image 20260220090934.png]]](https://i-blog.csdnimg.cn/direct/cc918af6ef0648ad836eb389427f1735.png)
![![[Pasted image 20260220091006.png]]](https://i-blog.csdnimg.cn/direct/298d47e604d64a71937c6ba6c210ab96.png)
函数和进程之间的相似性
exec/exit就像call/return
一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。
这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间。如下图
![![[Pasted image 20260213171956.png]]](https://i-blog.csdnimg.cn/direct/b5a7bc6f499541bdaed071ba0cb88706.png)
一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。
269



被折叠的 条评论
为什么被折叠?



