欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > 【Linux】进程控制

【Linux】进程控制

2024/10/23 8:46:19 来源:https://blog.csdn.net/weixin_74792326/article/details/142957737  浏览:    关键词:【Linux】进程控制

目录

1、进程创建

1.2、fork(补充)

1.2、写时拷贝(补充)

2、进程终止

3、进程等待

4、程序替换

5、自己写一个shell


1、进程创建

1.2、fork(补充)

//一次创建一批进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>#define N 5void runChlid()
{int cnt = 10;while(cnt)  //执行10秒之后自动退出{printf("I am child: %d, ppid:%d\n",getpid(),getppid());sleep(1);cnt--;}
}
int main()
{for(int i = 0; i < N; i++){pid_t id = fork();if(id == 0){runChild();exit(0);  //子进程结束//父进程往下走}  //父进程 再继续去循环  这样就可以一次性创建出5个子进程}sleep(1000);return 0;
}

父子进程或者兄弟进程,谁先运行,完全是由调度器决定的,谁先被放到运行队列里谁就先运行,这是不能被确定的。

1.2、写时拷贝(补充)

补充一个细节问题:OS是如何知道我们此时父子进程共享数据块,此时是需要发生写时拷贝的呢? 

在父进程创建了子进程时候,父进程的数据段就会由原来的读写段被暂时改为只读段,子进程也是只读。只读区不变。所以,不论是父进程还是子进程谁想写,就会触发系统层面的权限问题,但是在权限方面审核的时候,OS会识别出来,历史上这个区域是可以被写入的,只不过是暂时是只读状态。因此这时触发的权限问题,OS不会做异常异常处理,而是将异常转为 将数据拷贝一份,谁写的就将谁的页表重新映射,拷贝完之后在页表中将只读的标签去掉,就可以正常做修改了。

其实,写时拷贝本质上就是用时再用,延迟申请,按需申请!

2、进程终止

问题:为什么以前写代码main函数总是会返回return 0?1?2? 这个值给谁了?为什么要返回这个值?

进程退出场景:a、代码运行完毕,结果正确。(不关心)

b、代码运行完毕,结果不正确。(关心为什么)

c、代码异常终止

输入 echo $? //用来表示最近一个程序退出时的退出码

0 //这个0会被父进程即bash拿到,得到上一个程序的退出码

[]$  ./myproc

.........

[]$  echo $?

11   //假如11是myproc程序的退出码

[]$  echo $?

0   //打印第二遍为什么会变为0?  因为它的上一个是echo命令,echo命令执行是正确的,所以就是0

进程中,谁会关心我运行情况?一般而言是父进程要关心

结果为什么不正确?可以用return 的不同返回值数字,表征不同的出错原因,这些不同的数字就叫作退出码

总而言之,main函数的返回值本质表示:进程运行完成时是否正确的结果,如果不是,可以用不同的数字表示不同的出错原因。

退出码是数字 这些数字是给计算机看的,我们看的应该是一些错误信息。

#include<string.h>int main()
{for(int i = 0; i < 200; i++){printf("%d: %s\n", i, strerror(i)); //打印每个数字对应的错误信息}return 0;
}

系统提供的错误码和错误码描述是有对应关系的。 错误码表示退出原因,错误信息展示具体详细的错误。

那么,我们可不可以自己设计一套退出码体系呢?  答案是:当然可以

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>const char *errString[] = {"success","error 1","error 2","error 3","error 4","error 5",
};int main()
{for(int i = 0; i < 200; i++){printf("%d: %s\n", i, strerror(i)); //打印每个数字对应的错误信息}return 0;
}

结果正确或者说不正确,统一会采用进程的退出码来进行判定!!

问题:为什么以前写代码main函数总是会返回return 0?1?2? 这个值给谁了?为什么要返回这个值? 因此,这个问题就可以解决了。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>  int main()
{int ret = 0;char *p = (char*)malloc(1000*1000*1000*4);if(p == NULL)  //申请失败{printf("malloc error,%d: %s\n",errno,strerror(errno)); //知道错误码 错误信息ret = 1;//并且还可以将错误码转为进程的 退出码,让父进程也知道}else{//使用申请的内容printf("malloc success\n");}return ret;
}

[]$  ./myproc

malloc error, 12: Cannot allocate memory

[]$  echo $?

12 //退出码

errno  是C语言给我们提供的一个全局变量。保存最近一次执行的错误码。c语言调用库函数不成功的时候,返回值告诉我们这个函数的情况,结果对不对,至于结果不对的原因,比如当库函数调用失败了,C语言就会将我们的errno全局变量设置成对应的数字,这个数字表示我们当前调用的函数出错时的错误码。 如果连续调函数都失败了,这个errno每次都会被覆盖,它保留的时last error

如果代码出现了异常(第三种情况),退出码还有意义吗??答案是:本质可能代码没有跑完,进程的退出码无意义,我们不关心退出码。

因此,进程退出的时候,我们首先关心有没有发生异常,没出异常就是代码跑完了,再看结果是否是正确的(再看退出码);如果出异常了,比如野指针的时候就相当于你即将要访问一个虚拟地址,这个虚拟地址在页表当中并没有建立对应的映射关系,或者说建立了映射关系它的权限设置成立只读权限,这些问题都会被OS识别,OS就会向目标进程发信号。进程出现异常,本质是我们进程收到了对应的信号(父进程看有没有收到信号)!

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h> int main()
{printf("hello Linux\n");exit(12);  //让一个进程退出 12 这个数字是退出码//和return在main函数里是等价的
}
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h> void show()
{printf("hello show!\n);printf("hello show!\n);printf("hello show!\n);printf("hello show!\n);printf("hello show!\n);exit(13);printf("end show \n");printf("end show \n");printf("end show \n");int main()
{show();printf("hello Linux\n");exit(12);  
}

运行结果:

hello show!

hello show!

hello show!

hello show!

hello show!

退出码是13

结论:exit 在任意地方被调用,都表示调用进程直接退出

return在main函数外面,只表示当前函数返回,返回到main函数。

因此,return在其他函数中代表函数返回,在main函数中代表进程退出;而exit在任意地方都代表进程退出!

exit与_exit之间的区别

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>int main()
{printf("you can see me!\n");sleep(1);exit(11);//_exit(11);
}

上面的代码exit与_exit的结果是一样的,因为有\n,退出也是一样的。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>int main()
{printf("you can see me!");sleep(1);exit(11);}

printf没有加\n的时候数据会暂时放到缓冲区当中,所以上面的代码数据并没有立即刷出来,停了一秒,当整个程序退出的时候,系统才会将数据刷出来。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>int main()
{printf("you can see me!");sleep(1);_exit(11);}

没有打印出来you can see me!

_exit是系统调用接口,程序直接调用系统接口,直接在进程层面就将这个程序控制了,而exit会在应用层将你之前打开的文件..进行刷新(刷新缓冲区),然后exit再调_exit.因为先把结果刷新了(关闭所有打开的流,所有的缓存数据均被写入 )再退出,所以就可以看到数据。

printf一定是先把数据写入缓冲区中,等到合适的时候,再进行刷新。比如说遇到\n 、进程退出。

那么这个缓冲区绝对不在哪里??? 答案是:绝对不在内核里!因为如果在内核里,_exit和exit都会对这个缓存区进行刷新,因为OS不会做任何浪费空间的行为,维护缓冲区就一定要进行刷新,因此不在。它其实在用户区。

3、进程等待

是什么??

通过系统调用wait/waitpid,来对子进程状态进行检测与回收的功能!

为什么??

a、父进程不管子进程,子进程会僵尸,僵尸就会造成内存泄露。

b、僵尸状态的进程不能通过任何操作被杀死,只能等待父进程回收它

a和b是我们必须解决的

c、我们要通过进程等待获得子进程的退出情况,这样是为了知道我布置给子进程的任务,它完成的怎么样了。

c我们要么关心,要么不关心------可选的

怎么办??

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{pid_t id = fork();if(id < 0){perror("fork"); //perror是c语言的系统调用,会将错误码转化成错误码描述,//将对应的函数带上再打印出错误信息return 1;}else if(id == 0){//childint cnt = 5;while(cnt){printf("I am child, pid:%d, ppid:%d, cnt: %d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(0);  //子进程退出}else{//parentint cnt = 10;whlie(cnt){printf("I am father, pid:%d, ppid:%d, cnt: %d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}pid_t ret = wait(NULL);if(ret == id)//这个id就是要等待的子进程的pid{printf("wait success, ret: %d\n", ret);}sleep(5);}return 0;
}

上面代码的现象:前5s子进程运行,运行5s,子进程变为僵尸状态,父进程运行5s子进程此时还是僵尸进程,之后,父进程回收子进程,然后僵尸进程没有了,父进程单独运行5s

ret返回值刚好也是子进程的pid   那么,我们就看到子进程被回收了。

到目前位置,进程等待是必须的!

wait等待的是任意一个子进程,如果我们有多个子进程呢?

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>#define N 10void RunChild()
{int cnt = 5;while(1) //子进程执行5秒{printf("I am Child Process, pid: %d, ppid: %d\n",getpid(),getppid());sleep(1);cnt--;}
}int main()
{for(int i = 0; i < N; i++) //这个for循环因为执行流的原因,父进程会通过for循环创建一批子进程{pid_t id = fork(); //使用for循环创建出一批进程if(id == 0){RunChild();exit(0); //子进程退出}printf("create child process: %d success\n",id);//这句话只有父进程会执行//这个id值是父进程创建出的子进程的pid}sleep(10);//子进程执行5秒,父进程10秒,现在就要让父进程对子进程进行等待for(int i = 0; i < N; i++){pid_t id = wait(NULL);  //因为wait每次只等待一个进程退出,所以等待一批进程就要用for循环if(id > 0){printf("wait %d success\n",id);}}sleep(5);
}

如果子进程不退出,父进程默认在wait的时候,调用这个系统调用的时候不返回,这就叫阻塞状态。进程等待是不是只会等待硬件资源呢?并不是!现在这个进程就是在等待软件。

pid_ t waitpid(pid_t pid, int *status, int options);
返回值,等待一个进程成功了,返回值就是你所等待的那个进程的pid
a、 pid = -1,等待任意一个进程,这样写的话效果与wait一样
pid > 0,   等待进程id和pid相等的
<1> status是输出型参数:就是能通过函数的参数把值带出来的参数,我给函数传参不是为了给函数什么,而是为了让函数交给我们什么。
<2> int是被当作及部分来使用的   一个int占32位
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{pid_t id = fork();if(id < 0){perror("fork"); //perror是c语言的系统调用,会将错误码转化成错误码描述,//将对应的函数带上再打印出错误信息return 1;}else if(id == 0){//childint cnt = 5;while(cnt){printf("I am child, pid:%d, ppid:%d, cnt: %d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(1);  //子进程退出  退出码是1}else{//parentint cnt = 10;whlie(cnt){printf("I am father, pid:%d, ppid:%d, cnt: %d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}// pid_t ret = wait(NULL);int status = 0;pid_t ret = waitpid(id, &status, 0);//如果关心子进程的退出状态,//就给status这个参数,就可以得到进程的退出信息if(ret == id)//这个id就是要等待的子进程的pid{printf("wait success, ret: %d\n", ret);}sleep(5);}return 0;
}

运行结果:.........

wait success, ret: 26472(子进程的pid), status:256

为什么退出信息是256呢?我们设置的退出码不是1嘛????

0000 0000 0000 0000 <0000 0001> 0000 0000

次低8位是退出码

子进程的退出场景有几种??

三种。代码运行完毕,结果正确

代码运行完毕结果不正确

代码异常终止

父进程等待,期望获得子进程退出的哪些信息呢??

a、子进程代码是否异常?

b、没有异常,结果对吗? 结果对不对,可以有退出码来判断。不对是因为什么?

1 2 3 4 ..不同的退出码表示不同的出错原因!

低8位表示子进程运行的时候是否出异常

检测进程在运行的时候低7位是否为0,如果是0,就代表我们没有收到信号,我们的代码就没有异常。这一步就可以判断我们的代码是否跑完。 再通过次低8位,获得进程的退出状态,即进程的退出码。

还有一个问题,我们不能通过设置一个全局变量,在子进程退出的时候,只需要让子进程将这个变量进行修改,我们父进程就可以拿到子进程的状态,这样做可以吗?

答案是:不能,因为进程具有独立性,子进程将数据进行修改的时候,就会发生写时拷贝,子进程对数据修改,父进程是拿不到修改之后的数据的。因此,只能调用系统调用函数来获得子进程的退出状态。

当子进程执行完毕之后,子进程的退出信息都会被保存在子进程的PCB里。调用pid_t waitpid(pid_t id,int* status,int XXX)  就是读取子进程的task_struct内核数据结构对象。因此,这些行为只能由OS来完成。

pid_ t waitpid(pid_t pid, int *status, int options);
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) 正常终止那么状态为真。 检验status变量的信号位, 如果为0(0代表没有收到任何信号),那么就是1;如果检测到信号不为0,那么就不为1
WEXITSTATUS(status): WIFEXITED 非零,提取子进程退出码。(查看进程的退出码)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{pid_t id = fork();if(id < 0){perror("fork"); //perror是c语言的系统调用,会将错误码转化成错误码描述,//将对应的函数带上再打印出错误信息return 1;}else if(id == 0){//childint cnt = 5;while(cnt){printf("I am child, pid:%d, ppid:%d, cnt: %d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(1);  //子进程退出  退出码是1}else{//parentint cnt = 10;whlie(cnt){printf("I am father, pid:%d, ppid:%d, cnt: %d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}// pid_t ret = wait(NULL);int status = 0;pid_t ret = waitpid(id, &status, 0);//如果关心子进程的退出状态,//就给status这个参数,就可以得到进程的退出信息if(ret == id)//这个id就是要等待的子进程的pid{if(WIFEXITED(status))//WIFEXITED判断子进程是否异常,即是否由信号发出,为0代表没有信号发出,WIFEXITED(status)就为真{printf("进程是正常跑完的,退出码:%d\n",WEXITSTATUS(status));//WEXITSTATUS提取子进程的退出码}else{printf("进程出异常了\n"); //是子进程出异常了}printf("wait success, ret: %d\n", ret);}else{printf("wait failed!\n");//这是waipid函数调用失败了}sleep(5);}return 0;
}

多个进程:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>#define N 10void RunChild()
{int cnt = 5;while(cnt) //子进程执行5秒{printf("I am Child Process, pid: %d, ppid: %d\n",getpid(),getppid());sleep(1);cnt--;}
}int main()
{for(int i = 0; i < N; i++) //这个for循环因为执行流的原因,父进程会通过for循环创建一批子进程{pid_t id = fork(); //使用for循环创建出一批进程if(id == 0){RunChild();exit(i); //子进程退出}printf("create child process: %d success\n",id);//这句话只有父进程会执行//这个id值是父进程创建出的子进程的pid}sleep(10);//子进程执行5秒,父进程10秒,现在就要让父进程对子进程进行等待for(int i = 0; i < N; i++){//pid_t id = wait(NULL);  //因为wait每次只等待一个进程退出,所以等待一批进程就要用for循环int status = 0;pid_t id = waitpid(-1, &status, 0);//-1 代表等待任意一个进程if(id > 0)//等待子进程成功{printf("wait %d success,exit code: %d\n", id, WEXITSTATUS(status));//WEXITSTATUS(status)获得进程退出码}}sleep(5);
}
pid_ t waitpid(pid_t pid, int *status, int options);
options:
WNOHANG: pid 指定的子进程没有结束,则 waitpid() 函数返回 0 ,不予以等待。若正常结束,则返回该子进程的ID

等待进程的两种方式:当进程不退出时,一直阻塞等待;非阻塞轮询

非阻塞轮询:非阻塞+循环    非阻塞轮询+做自己的事情

ret_pid > 0 等待成功

ret_pid = 0 等待的条件还没有就绪  这种情况会就绪等待,直到大于0或小于0

ret_pid < 0 等待不成功

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>#define TASK_NUM 10
typedef void(*task_t)();
task_t task[TASK_NUM];//任务
void task1()
{printf("这是一个执行打印日志的任务");
}
void task2()
{printf("这是一个检测网络健康状态的任务");
}
void task3()
{printf("这是一个绘制图形界面的任务");
}//任务的管理代码
void InitTask()
{for(int i = 0;i < TASK_NUM; i++) tasks[i] = NULL;
}int AddTask(task_t t)
{int pos = 0;for(;pos < TASK_NUM;pos++){if(!task[pos]) break;}if(pos == TASK_NUM) return -1;task[pos] = t;return 0;
}
void DelTask()
{}void CheckTask()
{}void UpdateTask()
{}
void ExecuteTask()
{for(int i = 0; i < TASK_NUM; i++){if(!tasks[i] continue;tasks[i]();}
}int main()
{pid_t id = fork();if(id < 0){perror("fork"); //perror是c语言的系统调用,会将错误码转化成错误码描述,//将对应的函数带上再打印出错误信息return 1;}else if(id == 0){//childint cnt = 5;while(cnt){printf("I am child, pid:%d, ppid:%d, cnt: %d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(1);  //子进程退出  退出码是1}else{//parentint cnt = 10;whlie(cnt){printf("I am father, pid:%d, ppid:%d, cnt: %d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}// pid_t ret = wait(NULL);int status = 0;//pid_t ret = waitpid(id, &status, 0);//如果关心子进程的退出状态,//就给status这个参数,就可以得到进程的退出信息InitTask();AddTask(task1);AddTask(task2);AddTask(task3);   while(1) //轮询{pid_t ret = waitpid(id, &status, WNOHANG);  //非阻塞if(ret > 0) //说明等待成功了{if(WIFEXITED(status))//WIFEXITED判断子进程是否异常,即是否由信号发出,为0代表没有信号发出,WIFEXITED(status)就为真{printf("进程是正常跑完的,退出码:%d\n",WEXITSTATUS(status));//WEXITSTATUS提取子进程的退出码}else{printf("进程出异常了\n"); //是子进程出异常了}break;}else if(ret < 0){printf("wait failed!\n");//这是waipid函数调用失败了break;}else{//ret == 0//printf("子进程还没有退出,我再等等...\n");ExecuteTask();}}sleep(5);}return 0;
}

4、程序替换

先看一个现象(单进程版):

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{printf("before: I am a process, pid: %d, ppid: %d\n", getpid(), getppid());//这类方法的标准写法execl("usr/bin/ls", "ls","-a","-l",NULL);//执行一个指定的文件printf("after: I am a process, pid: %d, ppid: %d\n", getpid(), getppid());return 0;
}

我们自己写的代码执行了 ls 只打印了before没有 after,上面这个现象就是程序替换


程序替换的基本原理:

ls这个命令的程序和代码在磁盘上,当在调用程序替换函数的时候,就会用新程序的代码替换旧程序的代码,新程序的数据替换旧程序的数据。替换完之后,只需要将页表的右侧再重新调整一下,就好了,pcb,进程地址空间以及页表的左侧不变。替换完之后,让cpu执行当前新的入口地址,来执行新程序,我们只是将当前进程的代码和数据进行了替换而并没有创建新进程,这就叫程序替换。

多进程版:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{pid_t id = fork();if(id == 0){    //childprintf("before: I am a process, pid: %d, ppid: %d\n", getpid(), getppid());sleep(5);//这类方法的标准写法execl("usr/bin/ls", "ls","-a","-l",NULL);//执行一个指定的文件printf("after: I am a process, pid: %d, ppid: %d\n", getpid(), getppid());exit(0);}//fatherpid_t ret = waitpid(id, NULL, 0);if(ret > 0)printf("wait success,father pid: %d, ret id: %d\n",getpid(), ret);sleep(5);return 0;
}

上面代码实验现象:创建子进程,子进程去执行程序替换,执行ls命令,在子进程执行完毕之后,父进程对子进程进行等待回收。

那么,这个子进程发生进程替换为什么不会影响父进程呢?因为写时拷贝和进程具有独立性。那替换数据的时候可以进行写时拷贝,那么替换代码的时候也可以是发生写时拷贝吗?

答案是:是的!代码也可以实现写时拷贝! 当我们要想自己对代码区进行写入时,OS会对我们的行为进行拦截,但是现在我们调用的是系统函数,对代码区进行写时拷贝的工作是由OS来完成的,因此,是可以的。

程序替换有没有创建新进程?

答案是:没有!只进行程序和代码的替换工作。 因此,进程的内核数据结构就没有被释放和重新建立,只需要改某些字段就欧克了。

是父子关系发生写时拷贝,不是父子关系,直接替换。

为什么after没有被打印? 因为after属于老程序的代码,这个代码在execl之后,老代码早已被替换,因此不会执行。程序替换失败了?才可能执行后续的代码。 那么,exec系列的函数只有失败返回值,没有成功返回值!!

替换函数: 有六种exec系列的函数

#include <unistd.h>`
int exec l (const char *path, const char *arg, ...);
前面都是exec, l:list ,后面带l的就是参数一个一个传,并且是可变参数。
第一个参数是我如何找到该程序,并且所有的exec函数都一样。
从第二个参数开始,命令行怎么写,这个参数就怎么传,后面的参数代表找到程序之后如何执行这个程序,主要是要不要涵盖选项,涵盖哪些。
int exec lp (const char *file, const char *arg, ...);
p是PATH,第一个参数不用给路径,只需要给出文件就可以。后面的参数还是一样。
eg:execlp("ls","ls","-a","-l",NULL); 第一个ls是找到执行的程序,后面是选项
int exec le (const char *path, const char *arg, ...,char *const envp[]);
e:env(环境变量)
int exec v (const char *path, char *const argv[]); 字符串指针数组
v:vector 第一个参数是找到可执行程序 第二个是 将我们的选项放到指针数组里
eg:char *const myargv[] = {
                "ls",
                "-l",
                "-a",
                NULL
            };
execv("user/bin/ls",myargv);
ls是已经编译好的程序,并且它肯定也有main函数,有main函数肯定就有命令函参数,那么它的命令函参数是谁提供的?是execv系统调用由myargv传入的,execv是系统函数,系统获取了命令行参数然后传给ls的main函数就好了
在命令行当中所有的进程都是bash的子进程,因此,所有的进程在启动的时候,都是采用exec系列的函数进行启动执行的。所以,exec系列的函数承担的是一个加载器的效果!! 其实,execv就是代码层面的加载器。可以将可执行程序导到内存里,也可以获得命令行参数,执行ls调用main函数时,就是将myargv参数传进去的。
int exec vp (const char *file, char *const argv[]);
p就是只给文件名 第二个参数和上面相同

环境变量是什么时候给进程的?环境变量也是数据,创建子进程的时候,环境变量就已经被子进程继承下去了! 因此,程序替换中环境变量信息不会被替换!

事实上 , 只有 execve 是真正的系统调用 , 其它五个函数最终都调用 execve,

5、自己写一个shell

shell:是OS外的一层外壳程序,负责帮用户进行指令的执行,负责将指令交给OS,OS执行完之后通过shell将结果交给用户,因此它叫作外壳程序。shell/bash也是一个进程,指令执行的时候,本质就是自己创建子进程去执行的!

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " "
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
int lastcode = 0;    
int quit = 0;char commandline[LINE_SIZE];const char *getusername()
{return getenv("USER");
}const char *gethostname()
{return getenv("HOSTNAME");
}const char *getpwd()
{return getenv("PWD");
}void interact(char *cline, int size)
{printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),getpwd());//scanf("%s",commandline); //scanf是以空格结束的 输入的时候以空格作为分隔符,只会读入一个,因此不能用scanfchar *s = fgets(cline, size, stdin);assert(s);(void)s;//会面不会用s 避免警告提示//"abcd\n\0" 输入字符串之后会输入回车  那么我们需要将\n置0 它的下标是strlen(commandline)-1cline[strlen(cline)-1] = '\0';int spliststring(char cline[], char *_argv[])//输出型参数
{int i = 0;argv[i++] = strtok(cline, DELIM);while(_argv[i++] = strtok(NULL,DELIM)); //strtok一次切割一个字符串 所以需要循环调用return i - 1; //i 是命令行参数个数  因为最后是NULL,所以需要-1 
}
void NormalExcute(char *_argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if(id == 0){//让子进程执行命令execvpe(_argv[0],_argv,environ);exit(EXIT_CODE);//程序替换有返回值必定出错 如果出错了就以这样的方式退出}else{int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) //等待成功{lastcode = WEXITSTATUS(status);}}
}
int bulidCommand(argv,argc)
{if(argc == 2 && strcmp(argv[0],"cd") == 0){chdir(argv[1]); //更改当前程序的路径getpwd();sprintf(getenv("PWD"),"PWD=%s",argv[1]);//格式化到环境变量里return 1;}return 0;
}   
int main()
{extern char **environ;char commandline[LINE_SIZE];char *argv[ARGC_SIZE];while(!quit) //shell本质上是一个死循环{//2、交互问题,获取命令行interact(commandline ,sizeof(commandline));//printf("echo : %s\n", commandline);//commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"将一个大字符串切割成四个小字符串,那么小字符串的地址保存在哪里???//3、子串分割问题,解析命令行int argc = spliststring(commandline, argv);if(argc == 0) countinue; //切割之后发现是空串 继续输入//4、指令的判断 如果是内键命令就让程序自己执行,不需要子进程执行//内键命令本质就是shell内部的一个函数int n = bulidCommand(argv,argc);//debug//for(int i = 0;argv[i]; i++)printf("[%d]: %s\n", i, argv[i]);//5、普通命令的执行if(!n) NormalExcute(argv);//传入刚刚解析的argv(也就是需要执行的命令)}return 0;
}
1. 获取命令行
2. 解析命令行
3. 建立一个子进程( fork
4. 替换子进程( execvp
5. 父进程等待子进程退出( wait

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com