fork之后就会创建则进程,数据、堆、栈有两份,代码仍然为一份但是这个代码段成为两个进程的共享代码段都从fork函数中返回,当父子进程有一个想要修改数据或者堆栈时,两个进程真正分裂子进程在创建的时候会复制父进程的代码。
不管是共用代码还是复制代码,这就相当于父进程创建了一个和自己功能完全相同的进程,这样一来,子进程就只能进行和父进程一样的操作动作,这样做实际意义并不大,那怎么让子进程干和父进程不一样的事情?
程序替换:子程序通常调用exec函数,替换掉该进程的用户空间代码和数据,从新程序的启动历程开始执行,exec函数并不是创建进程,所以exec之后,进程的id并未改变
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 execvpe(const char *file, char *const argv[], char *const envp[]);
l(list):表示采用列表
v(vector):参数用数组
p(path):自动搜索环境变量
e(env):自己维护的环境变量
集体操作看下面代码
#include<stdlib.h>
#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);
execvp("ps", argv);
execve("/bin/ps", argv, envp);
exit(0);
}
这个程序只需要执行一个上述 exec 函数,就可将这段代码生成的可执行程序编程ps -ef 这条命令,上述代码只有一个进程,换成两个进程,基本操作也是一样的,如下面代码,实现最简shell:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
char *strs[128] = {};
int argc = 0;
void stok(char *buf, const char *ch)
{
if(buf == NULL)
{
return;
}
char *token = strtok(buf, ch);
strs[argc] = token;
while((token = strtok(NULL, ch)) != NULL)
{
argc++;
strs[argc] =token;
}
strs[argc+1] = NULL;
}
int main()
{
char buf[256] = {};
while(1)
{
pid_t pid = fork();
if(pid == 0)
{
printf("myshell#");
scanf("%[^\n]%*c", buf);
stok(buf, " "); //打断buf字符串
execvp(strs[0], strs);
}
else
{
int status = 0;
pid_t id = waitpid(pid,&status, 0);
if(id < 0)
{
printf("wait runing error!\n");
}
}
}
return 0;
}
在这段代码中,子程序的代码被替换,父进程调用waitpid阻塞式等待子进程返回,子进程返回之后回收子进程的资源,while循环中使用fork,创建子进程,然后在子进程中读取键盘上的输入,并将读进的字符在串在空格处打断,并在每个子串的最后加上\0,让strs数组中的每个指针都依次指向这些子串,完成之后在strs最后一个元素的后面加上一个空指针,这样做的是为了满足execvp的格式要求,然后调用execvp进行程序替换,替换完完毕之后子程序退出,父进程会受到子进程的资源,然后进入下一次循环。
scanf("%[^\n]%*c", buf);
使用snanf是最后的回车也会被输入,上面这句代码的意思是:当敲下回车时,这个回车会被屏蔽掉,也可写为:
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;