在Linux中,fork是非常重要的函数。它从已经存在的进程中创建一个新的进程,新进程为子进程,原进程为父进程。
头文件:
pid_t fork(void);
返回值:子进程返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核将:
fork之前父进程独立执行,fork之后,父子两个执行流分别执行,两个进程共享同一份代码。写时拷贝(写入时进行深拷贝)确保了数据的独立。
❓为什么不在创建子进程的时候就重新开辟空间把数据分开,而要写时拷贝?(写时拷贝的优点)
这里首先提出两个问题:
C/C++
中 main
函数 return 0
,是给谁 return
?return 0
,return
其他值可以吗?进程代码跑完,结果是否正确:正确 return 0
,错误应该 return
非零,非零的值不同,表示不同的失败原因。这个非零的值又叫做进程退出码。它表征了进程的退出信息,是需要让父进程读取的(return 给了父进程)。
验证:
写一段代码直接返回一个非零值。
#include
int main()
{
return 123;
}
[CegghnnoR@VM-4-13-centos 2022_8_16]$ ./mytest
[CegghnnoR@VM-4-13-centos 2022_8_16]$ echo $?
123
[CegghnnoR@VM-4-13-centos 2022_8_16]$ echo $?
0
运行后 echo $?
。$?
表示在 bash 中,最近一次执行完毕时,对应进程的退出码。再 echo
一次就变成 0
了。
在使用指令时,也可以通过 echo $?
查看返回值:
[CegghnnoR@VM-4-13-centos 2022_8_16]$ ls
makefile mytest mytest.c
[CegghnnoR@VM-4-13-centos 2022_8_16]$ echo $?
0
[CegghnnoR@VM-4-13-centos 2022_8_16]$ ls abc
ls: cannot access abc: No such file or directory
[CegghnnoR@VM-4-13-centos 2022_8_16]$ echo $?
2
ls
执行成功返回 0,执行失败返回 2。
在 main
函数中 return
在自己代码的任意地点,调用 exit()
括号中传入退出码
_exit()
类似,但是有区别:
#include
#include
#include
int main()
{
for (int i = 0; i < 100; ++i)
{
printf("hello world");
sleep(1);
_exit(111); // 不显示hello world
//exit(111); // 显示hello world
return 10;
}
return 123;
}
因为没有 \n
,所以 printf
执行完 hello world 不会出现在屏幕上,而是在缓冲区。
exit 终止进程,刷新缓冲区
_exit 终止进程,不会有任何刷新操作
实际上,_exit 属于系统调用,exit 的底层实现会去调用它。
进程 = 内核结构 + 进程代码 + 数据
当一个进程不跑了,首先进入Z状态,然后等待父进程回收退出信息,然后将进程设置为X状态,释放内核结构以及进程代码和数据。
其实,操作系统可能不会释放进程的内核数据结构,Linux内部会维护一个废弃的数据结构链表,原来的内核数据结构仅仅设置为无效,出现新的进程,就直接初始化一个废弃的数据结构,节省了开辟空间的消耗。这种操作叫做 slab分派器,废弃的数据结构链表叫做 数据结构缓冲池
kill -9
也杀不死,因为你无法杀死一个已经死去的进程。头文件:
pid_t wait(int* status);
返回值:成功返回被等待进程 pid,失败返回 -1
参数:输出型参数,获取子进程退出状态,不关心可以设置为NULL
例子:
父进程先等待20s,在这20s内把子进程杀掉,20s后父进程等待,回收子进程,打印子进程pid。再过20s父进程死亡,被 bash 回收。
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if (id == 0)
{
//child
while (1)
{
printf("我是子进程,pid: %d,正在运行...\n", getpid());
sleep(1);
}
}
else
{
printf("我是父进程,pid: %d,准备等待子进程\n", getpid());
sleep(20);
pid_t ret = wait(NULL);
if (ret < 0)
{
printf("等待失败!\n");
}
else
{
printf("等待成功,result: %d\n", ret);
}
sleep(20);
}
return 0;
}
pid_ t waitpid(pid_t pid, int* status, int options);
返回值:
等待子进程成功,返回子进程的 pid
等待失败,返回值 <0
参数:
pid
:传入一个子进程的 pid 表示指定等待该子进程;-1 表示等待任意进程
status
:输出型参数,同上
options
:0 阻塞等待(阻塞状态地等,阻塞状态不止可以等硬件资源,也可以等软件)
输出型参数 status 如何查看?只需要关心它的低16个比特位,分为三部分:
正常终止只要关注次低8位,表示子进程的退出码。
测试:子进程运行5s后死亡,退出码123,父进程等待子进程死亡,等待成功,打印子进程退出码。
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if (id == 0)
{
//child
int cnt = 5;
while (cnt--)
{
printf("我是子进程,pid: %d,正在运行...\n", getpid());
sleep(1);
}
exit(123);
}
else
{
int status = 0;
printf("我是父进程,pid: %d,准备等待子进程\n", getpid());
pid_t ret = waitpid(id, &status, 0);
if (ret > 0)
{
printf("等待成功, ret: %d, 所等待的子进程的退出码:%d\n", ret, (status >> 8) & 0xFF);
}
}
return 0;
}
使用位运算(status >> 8) & 0xFF
可以得到次低8位。
结果符合预期:
[CegghnnoR@VM-4-13-centos 2022_8_16]$ ./mytest
我是父进程,pid: 27225,准备等待子进程
我是子进程,pid: 27226,正在运行...
我是子进程,pid: 27226,正在运行...
我是子进程,pid: 27226,正在运行...
我是子进程,pid: 27226,正在运行...
我是子进程,pid: 27226,正在运行...
等待成功, ret: 27226, 所等待的子进程的退出码:123
如果异常退出,是因为这个进程收到了特定的信号,比如我们之前常用的 kill -9
,其中9就是一个信号。
kill -l
显示的就是所有的信号:
[CegghnnoR@VM-4-13-centos 2022_8_16]$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
测试:子进程无限循环,父进程阻塞等待。最后我们手动杀死子进程,父进程等待成功,打印退出信号。status & 0x7F
即可获得低7位:
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if (id == 0)
{
//child
while (1)
{
printf("我是子进程,pid: %d,正在运行...\n", getpid());
sleep(1);
}
}
else
{
int status = 0;
printf("我是父进程,pid: %d,准备等待子进程\n", getpid());
pid_t ret = waitpid(id, &status, 0);
if (ret > 0)
{
printf("等待成功, ret: %d, 所等待的子进程的退出码:%d, 退出信号:%d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
}
}
return 0;
}
结果符合预期:
[CegghnnoR@VM-4-13-centos 2022_8_16]$ ./mytest
我是父进程,pid: 32299,准备等待子进程
我是子进程,pid: 32300,正在运行...
我是子进程,pid: 32300,正在运行...
我是子进程,pid: 32300,正在运行...
等待成功, ret: 32300, 所等待的子进程的退出码:0, 退出信号:9
[CegghnnoR@VM-4-13-centos 2022_8_16]$ kill -9 32300
除了我们手动发信号,平时运行代码遇到错误程序崩溃,其实是由操作系统给进程发了信号。
比如下面,0作除数的情况:
int main()
{
pid_t id = fork();
if (id == 0)
{
//child
int a = 5/0; //除以0
}
else
{
int status = 0;
printf("我是父进程,pid: %d,准备等待子进程\n", getpid());
pid_t ret = waitpid(id, &status, 0);
if (ret > 0)
{
printf("等待成功, ret: %d, 所等待的子进程的退出码:%d, 退出信号:%d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
}
}
return 0;
}
如果我们不管编译错误,执意运行:
[CegghnnoR@VM-4-13-centos 2022_8_16]$ ./mytest
我是父进程,pid: 3483,准备等待子进程
等待成功, ret: 3484, 所等待的子进程的退出码:0, 退出信号:8
通过查表发现 8 号是 SIGFPE
,表示数学相关的异常,如被0除,浮点溢出,等等
再比如,对空指针解引用:
if (id == 0)
{
//child
int* a = NULL;
*a = 10;
}
[CegghnnoR@VM-4-13-centos 2022_8_16]$ ./mytest
我是父进程,pid: 4795,准备等待子进程
等待成功, ret: 4796, 所等待的子进程的退出码:0, 退出信号:11
信号11 SIGSEGV,表示非法内存访问。
也可以不用位运算,而通过系统自带的宏来提取退出码。
WIFEXITED(status)
:查看进程是否正常退出
WEXITSTATUS(status)
:若正常退出,查看进程的退出码
正确写法:
else
{
//parent
int status = 0;
printf("我是父进程,pid: %d,准备等待子进程\n", getpid());
pid_t ret = waitpid(id, &status, 0);
if (ret > 0)
{
if (WIFEXITED(status))
{
printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));
}
}
}
常见进程退出:
上面讲的正常终止,其实就是前两种情况,异常终止则对应第三种情况。
退出码和退出信号,优先查看退出信号。
阻塞等待:父进程在等待子进程时,只要子进程没有结束,父进程就不进行任何操作,像上面的就是阻塞等待。
非阻塞等待:父进程等待子进程时,如果子进程没有结束,那么父进程会接受0来表示子进程还没有结束,然后继续执行父进程代码,不继续等待。
waitpid 的非阻塞等待可以通过 man 手册查询:
RETURN VALUE
waitpid(): on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned. On error, -1 is returned.
等待成功子进程结束则返回子进程id,如果指定了选项 WNOHANG,子进程未结束返回0,出错返回-1.
下面是一个基于非阻塞的轮询等待,即每隔一会非阻塞等待一次
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if (id == 0)
{
// 子进程
while (1)
{
printf("这是子进程,pid:%d, ppid:%d\n", getpid(),getppid());
sleep(5);
break;
}
exit(202);
}
else if(id > 0)
{
int status = 0;
// 父进程,基于非阻塞的轮询等待
while (1)
{
pid_t ret = waitpid(-1, &status, WNOHANG);
if (ret > 0)
{
printf("等待成功:%d, exit sig: %d, exit code: %d\n", ret, status & 0x7F, (status >> 8) & 0xFF);
break;
}
else if (ret == 0)
{
printf("等待成功,子进程未结束\n");
sleep(1);
}
else
{
// 出错
}
}
}
return 0;
}
[CegghnnoR@VM-4-13-centos 2022_9_24]$ ./test
等待成功,子进程未结束
这是子进程,pid:8292, ppid:8291
等待成功,子进程未结束
等待成功,子进程未结束
等待成功,子进程未结束
等待成功,子进程未结束
等待成功:8292, exit sig: 0, exit code: 202
要想让父进程在轮询等待的间隔期间做其他事,可以写一个方法集,即用vector存函数指针,然后父进程一遍轮询等待,一边遍历vector数组进行回调函数。
#include
#include
#include
#include
#include
#include
#include
typedef void (*handler_t)();
// 方法集
std::vector<handler_t> handlers;
void fun1()
{
printf("hello, 我是方法1\n");
}
void fun2()
{
printf("hello, 我是方法2\n");
}
void Load()
{
// 加载方法
handlers.push_back(fun1);
handlers.push_back(fun2);
}
int main()
{
pid_t id = fork();
if (id == 0)
{
while (1)
{
printf("这是子进程,pid:%d, ppid:%d\n", getpid(),getppid());
sleep(5);
break;
}
exit(202);
}
else if(id > 0)
{
int status = 0;
while (1)
{
pid_t ret = waitpid(-1, &status, WNOHANG);
if (ret > 0)
{
printf("等待成功:%d, exit sig: %d, exit code: %d\n", ret, status & 0x7F, (status >> 8) & 0xFF);
break;
}
else if (ret == 0)
{
printf("等待成功,子进程未结束\n");
if (handlers.empty()) Load();
for (auto f : handlers)
{
f();
}
sleep(1);
}
else
{
// 出错
}
}
}
return 0;
}
[CegghnnoR@VM-4-13-centos 2022_9_24]$ ./test
等待成功,子进程未结束
hello, 我是方法1
hello, 我是方法2
这是子进程,pid:17949, ppid:17948
等待成功,子进程未结束
hello, 我是方法1
hello, 我是方法2
等待成功,子进程未结束
hello, 我是方法1
hello, 我是方法2
等待成功,子进程未结束
hello, 我是方法1
hello, 我是方法2
等待成功,子进程未结束
hello, 我是方法1
hello, 我是方法2
等待成功,子进程未结束
hello, 我是方法1
hello, 我是方法2
等待成功:17949, exit sig: 0, exit code: 202
通过fork创建的子进程执行的是父进程的代码片段,如果我们要让子进程执行全新的程序呢?
我们一般在Linux程序设计的时候,往往需要让子进程干两件事情
程序替换的原理:
那么如何把磁盘上的一个新的程序导入到内存中呢,这要靠os来完成,作为程序员可以通过系统调用来完成。
int execl(const char *path, const char *arg, ...);
我们如果想执行一个全新的程序,需要做哪几件事情?
执行 /usr/bin 下的 ls ,也就是平时敲的ls命令
#include
#include
int main()
{
//ls -a -i
printf("此进程pid:%d\n", getpid());
execl("/usr/bin/ls", "ls", "-l", "-a", NULL);
printf("执行完毕,此进程pid:%d", getpid());
return 0;
}
[CegghnnoR@VM-4-13-centos 2022_9_30]$ ./myexec
此进程pid:19513
total 28
drwxrwxr-x 2 CegghnnoR CegghnnoR 4096 Sep 30 20:36 .
drwxrwxr-x 16 CegghnnoR CegghnnoR 4096 Sep 30 20:05 ..
-rw-rw-r-- 1 CegghnnoR CegghnnoR 66 Sep 30 20:31 makefile
-rwxrwxr-x 1 CegghnnoR CegghnnoR 8464 Sep 30 20:36 myexec
-rw-rw-r-- 1 CegghnnoR CegghnnoR 237 Sep 30 20:36 myexec.c
由结果可以看到,printf("执行完毕,此进程pid:%d", getpid());
这条代码没有执行,这是因为该进程在运行到 execl
时被替换了。
execl
函数不用判断返回值,因为只要替换成功了,就不会有返回值, 如果失败,则返回错误码,然后继续向后执行当前程序。
下面这段代码,让子进程去执行ls程序,父进程等待:
#include
#include
#include
#include
int main()
{
printf("此为父进程,pid:%d\n", getpid());
pid_t id = fork();
if (id == 0)
{
// child
// 让子进程执行全新的程序
printf("此为子进程,pid:%d\n", getpid());
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
exit(1); // 执行此条语句,说明execl失败了
}
// 父进程
int status = 0;
int ret = waitpid(id, &status, 0);
if (ret == id)
{
sleep(2);
printf("父进程等待成功\n");
}
return 0;
}
[CegghnnoR@VM-4-13-centos 2022_9_30]$ ./myexec
此为父进程,pid:2473
此为子进程,pid:2474
total 28
drwxrwxr-x 2 CegghnnoR CegghnnoR 4096 Oct 2 10:18 .
drwxrwxr-x 16 CegghnnoR CegghnnoR 4096 Sep 30 20:05 ..
-rw-rw-r-- 1 CegghnnoR CegghnnoR 66 Sep 30 20:31 makefile
-rwxrwxr-x 1 CegghnnoR CegghnnoR 8720 Oct 2 10:18 myexec
-rw-rw-r-- 1 CegghnnoR CegghnnoR 795 Oct 2 10:18 myexec.c
父进程等待成功
之前我们讲过,子进程和父进程的代码和数据是共享的,只有在需要修改时才会发生写时拷贝,进程替换也属于修改,也会发生写时拷贝,所以子进程的程序替换不会影响父进程。
也是程序替换函数,和 execl() 只有传参方式的区别
int execv(const char *path, char *const argv[]);
execl 后面是可变参数,execv 则是传入一个指针数组。
#include
#include
#include
#include
int main()
{
printf("此为父进程,pid:%d\n", getpid());
pid_t id = fork();
if (id == 0)
{
// child
// 让子进程执行全新的程序
printf("此为子进程,pid:%d\n", getpid());
char* const argv_[] = {(char*)"ls", (char*)"-a", (char*)"-l", (char*)"-i", NULL};
execv("/usr/bin/ls", argv_);
//execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
exit(1); // 执行此条语句,说明execl失败了
}
// 父进程
int status = 0;
int ret = waitpid(id, &status, 0);
if (ret == id)
{
sleep(2);
printf("父进程等待成功\n");
}
return 0;
}
int execlp(const char *file, const char *arg, ...);
file 参数填你想执行的程序名,执行的时候会通过环境变量PATH去搜索。
用 execlp 来替换 ls:
execlp("ls", "ls", "-a", "-l", NULL);
int execvp(const char *file, char *const argv[]);
char* const argv_[] = {(char*)"ls", (char*)"-a", (char*)"-l", (char*)"-i", NULL};
execvp("ls", argv_);
int execle(const char *path, const char *arg,
..., char * const envp[]);
前面两个参数上面都讲过,最后一个参数 char * const envp[]
的作用是将环境变量传递给目标进程
总结:exec+
l(list):填入多个参数,
v(vector):填入一个指针数组,
p(path):表示直接写程序名,不带p的需要写程序路径。
e(env):表示自己维护环境变量。
#include
#include
#include
#include
#include
#include
#define SEP " "
#define NUM 1024
#define SIZE 128
char command_line[NUM];
char* command_args[SIZE];
int main()
{
while (1)
{
// 1.显示提示符
printf("[张三@我的主机名 当前目录]# ");
fflush(stdout);
// 2.获取用户输入
memset(command_line, '\0', sizeof(command_line)*sizeof(char));
fgets(command_line, NUM, stdin);
command_line[strlen(command_line) - 1] = '\0'; // 去除\n
// 3.字符切分 "ls -a -l -i" -> "ls" "-a" "-l" "-i"
command_args[0] = strtok(command_line, SEP);
int index = 1;
// 为ls命令增加--color=auto选项,给展示的文件名添加颜色
if (strcmp(command_args[0], "ls") == 0)
command_args[index++] = (char*)"--color=auto";
while (command_args[index++] = strtok(NULL, SEP));
// 创建进程,执行
pid_t id = fork();
if (id == 0)
{
// child
// 程序替换
execvp(command_args[0], command_args);
exit(1);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if (ret > 0)
{
printf("等待子进程成功:sig: %d, code: %d\n", status & 0x7F, (status >> 8) & 0xFF);
}
}
return 0;
}
[CegghnnoR@VM-4-13-centos 2022_9_30]$ ./myshell
[张三@我的主机名 当前目录]# ls
makefile mycmd.cpp myexec.c myshell myshell.c
等待子进程成功:sig: 0, code: 0
[张三@我的主机名 当前目录]# ls -l
total 28
-rw-rw-r-- 1 CegghnnoR CegghnnoR 78 Oct 2 19:03 makefile
-rw-rw-r-- 1 CegghnnoR CegghnnoR 93 Oct 2 14:18 mycmd.cpp
-rw-rw-r-- 1 CegghnnoR CegghnnoR 634 Oct 2 15:22 myexec.c
-rwxrwxr-x 1 CegghnnoR CegghnnoR 8984 Oct 2 19:27 myshell
-rw-rw-r-- 1 CegghnnoR CegghnnoR 1239 Oct 2 19:27 myshell.c
等待子进程成功:sig: 0, code: 0
[张三@我的主机名 当前目录]# ls
makefile mycmd.cpp myexec.c myshell myshell.c
等待子进程成功:sig: 0, code: 0
其实目前还存在一个问题:cd..
无效
[张三@我的主机名 当前目录]# pwd
/home/CegghnnoR/code/2022_9_30
等待子进程成功:sig: 0, code: 0
[张三@我的主机名 当前目录]# cd ..
等待子进程成功:sig: 0, code: 1
[张三@我的主机名 当前目录]# pwd
/home/CegghnnoR/code/2022_9_30
等待子进程成功:sig: 0, code: 0
其实问题的原因也很简单,因为我们的程序是给子进程运行的,cd…改变的是子进程的工作目录,子进程运行完就结束了,没有切实地改变父进程的工作目录。
而进程之间是相互独立的,我们并不能让子进程去改变父进程的工作目录,所以像cd这类命令,只能由父进程自己来执行。这类由shell自己执行的命令,称为内建(bind-in)命令
代码如下:
直接定义一个ChangeDir函数,当输入的是cd命令就直接调用这个函数,而不是创建子进程。
chdir
是系统调用接口,可以更改工作目录
#include
#include
#include
#include
#include
#include
#define SEP " "
#define NUM 1024
#define SIZE 128
char command_line[NUM];
char* command_args[SIZE];
int ChangeDir(const char* new_path)
{
chdir(new_path);
return 0;
}
int main()
{
while (1)
{
// 1.显示提示符
printf("[张三@我的主机名 当前目录]# ");
fflush(stdout);
// 2.获取用户输入
memset(command_line, '\0', sizeof(command_line)*sizeof(char));
fgets(command_line, NUM, stdin);
command_line[strlen(command_line) - 1] = '\0'; // 去除\n
// 3.字符切分 "ls -a -l -i" -> "ls" "-a" "-l" "-i"
command_args[0] = strtok(command_line, SEP);
int index = 1;
if (strcmp(command_args[0], "ls") == 0)
command_args[index++] = (char*)"--color=auto";
while (command_args[index++] = strtok(NULL, SEP));
// 如果是cd命令,则直接调用ChangeDir
if (strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL)
{
ChangeDir(command_args[1]);
continue;
}
// 创建进程,执行
pid_t id = fork();
if (id == 0)
{
// child
// 程序替换
execvp(command_args[0], command_args);
exit(1);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if (ret > 0)
{
printf("等待子进程成功:sig: %d, code: %d\n", status & 0x7F, (status >> 8) & 0xFF);
}
}
return 0;
}
[张三@我的主机名 当前目录]# pwd
/home/CegghnnoR/code/2022_9_30
等待子进程成功:sig: 0, code: 0
[张三@我的主机名 当前目录]# cd ..
[张三@我的主机名 当前目录]# pwd
/home/CegghnnoR/code
等待子进程成功:sig: 0, code: 0
环境变量的数据,在进程的上下文中
export也是内建命令,如果我们要导出环境变量,需要给父进程提供一个函数,当我们使用export命令时直接调用这个函数即可。
putenv
是系统调用接口,可以用来添加或改变环境变量
char env_buffer[NUM];
void PutEnvInMyShell(char* new_env)
{
putenv(new_env);
}
int main()
{
while (1)
{
// 1.显示提示符
// 2.获取用户输入
// 3.字符切分 "ls -a -l -i" -> "ls" "-a" "-l" "-i"
// ...
if (strcmp(command_args[0], "export") == 0 && command_args[1] != NULL)
{
strcpy(env_buffer, command_args[1]);
PutEnvInMyShell(env_buffer);
continue;
}
// 创建进程,执行
// ...
}
return 0;
}