前言
本期分享进程控制的内容。
博主水平有限,不足之处望请斧正!
进程的控制主要分为四点:
通过fork
创建。
fork
创建子进程的函数。(使用已经介绍过)
task_struct
对象task_struct
对象的大部分属性拷贝给子进程的task_struct
对象进程list
pid
(在此之前,核心代码已经执行完毕)创建子进程后,父子进程会先共享数据和代码,当页表检测到任意一方尝试写入,就会发生写时拷贝,使得一人有一份数据和代码。此后才能写入。
这样有什么好处?
之前使用fork
就感觉很奇怪,if
else if
怎么能同时进,返回值怎么能返回两个?这里就可以解释了。
fork
返回pid
之前,fork核心代码(创建子进程的工作)已经执行完,父子进程已经分出两个执行流,对于fork剩下的代码都要执行。
if
/ else if
/ else
。是分别进入,不是同时进入。每次写main函数
,都要写return 0;
,有什么用呢?
可以说,我们写代码是为了完成某件事情。但我们怎么知道事情完成得如何?
return 0;
就是为了通过返回值确定“事情”完成得咋样。(很像僵尸进程的父进程获取子进程退出信息)
0
就是退出码!
退出码:进程退出信息的标识
一般0表示正常,非0表示错误(不同的非0值,可标识不同的错误)。
?
?
永远记录最近一次进程结束对应的退出码。
int add(int x, int y)
{
return x + y;
}
int main()
{
int ret = add(1, 1) + 1;
if(ret != 2)
return 1;
else
return 0;
}
[bacon@VM-12-5-centos 4]$ ./myproc
[bacon@VM-12-5-centos 4]$ echo $?
1
[bacon@VM-12-5-centos 4]$ echo $?
0
[bacon@VM-12-5-centos 4]$ echo $?
0
给结果+1,答案就错了,main返回1。
诶?怎么再看退出码就变了?
?
永远记录最近一次进程结束对应的退出码,而echo
也是进程,正常执行就返回0了。
但退出码对人不太友好,所以可以转换成具体信息
strerror
NAME
strerror, strerror_r - return string describing error number
SYNOPSIS
#include
char *strerror(int errnum);
用一下:
int main()
{
for(int i =0; i <100; ++i)
{
printf("[%d]: %s\n", i, strerror(i));
}
return 0;
}
[bacon@VM-12-5-centos 4]$ ./myproc
[0]: Success
[1]: Operation not permitted
[2]: No such file or directory
[3]: No such process
[4]: Interrupted system call
[5]: Input/output error
[6]: No such device or address
[7]: Argument list too long
[8]: Exec format error
[9]: Bad file descriptor
[10]: No child processes
//...
return 0;
return !0;
(退出码发挥作用)main函数
返回这个我们一直都在用,不多说。
exit
NAME
exit - cause normal process termination
SYNOPSIS
#include
void exit(int status);
DESCRIPTION
The exit() function causes normal process termination and the value of status & 0377 is returned to the parent
(see wait(2))
用一下:
int main()
{
printf("hello world\n");
exit(10);
}
[bacon@VM-12-5-centos 4]$ ./myproc
hello world
[bacon@VM-12-5-centos 4]$ echo $?
10
_exit
NAME
_exit, _Exit - terminate the calling process
SYNOPSIS
#include
void _exit(int status);
#include
void _Exit(int status);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
_Exit():
_XOPEN_SOURCE >= 600 || _ISOC99_SOURCE ||
_POSIX_C_SOURCE >= 200112L;
or cc -std=c99
DESCRIPTION
The function _exit() terminates the calling process "immediately". Any
open file descriptors belonging to the process are closed; any children
of the process are inherited by process 1, init, and the process's par‐
ent is sent a SIGCHLD signal.
用一下:
int main()
{
printf("hello world\n");
_exit(20);
}
[bacon@VM-12-5-centos 4]$ ./myproc
hello world
[bacon@VM-12-5-centos 4]$ echo $?
20
看这么区别不大?exit
底层就是_exit
实现的。
但是他们真的没有区别吗?来看一个例子。
int main()
{
printf("hello world");
sleep(2);
exit(10);
}
[bacon@VM-12-5-centos 4]$ ./myproc
hello world[bacon@VM-12-5-centos 4]$
exit
:可以看到,因为没有\n来刷新缓冲区,先sleep2秒,程序退出的时候才刷新缓冲区打印数据。
int main()
{
printf("hello world");
sleep(2);
_exit(20);
}
[bacon@VM-12-5-centos 4]$ ./myproc
[bacon@VM-12-5-centos 4]$
_exit
:居然并没有打印数据,那说明,exit
终止进程会主动刷新缓冲区,_exit
不会!
为什么要多搞个exit
,直接一个会主动刷新缓冲区的_exit
不香吗?
_exit
是系统调用,exit
是库函数,后者更底层。
exit
底层就是_exit
实现的 ==> exit
能刷新,_exit
肯定也能刷新 | 但_exit
没有刷新 | ==> 缓冲区肯定不在操作系统层面(不然两者都刷) ==> 只能在用户层面。
也因此,只有用户层面的exit
才能刷新。_exit
不是不想刷新,而是他的地盘根本没缓冲区给他刷。
*具体等基础IO讲
先前提到僵尸进程的资源释放是个问题,可能造成内存泄漏。进程等待就可以来解决这个问题了。不仅如此,父进程需要知道子进程的任务完成得如何,也可以通过进程等待的方式回收子进程资源,获取子进程退出信息。
先用个简单的wait,见见猪跑
pid_t wait(int* status)
作用:等待任意子进程
返回值:等待成功返回被等待进程的pid,等待失败返回-1
参数:输出型参数,获取子进程退出状态,不关心可以设为NULL(方便演示,我们先不用,见见猪跑)
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 10;
while(cnt)
{
printf("child process: pid=%d ppid=%d; on:%d\n", getpid(), getppid(), cnt--);
sleep(1);
}
printf("child process exit now!\n");
exit(0);
}
sleep(15);
pid_t ret = wait(NULL);
if(id > 0)
printf("wait success: %d\n", ret);
sleep(5);
return 0;
}
创建子进程,10秒后子进程退出,退出再过5秒,父进程等待到子进程,回收子进程资源,获取子进程退出信息。
我们成功看到父进程对子进程的等待,回收了子进程资源,获取了子进程退出信息(用status获取,这里还不关心)。
那我想指定等待的进程呢?看waitpid
这个系统调用。
pid_t waitpid
NAME
waitpid - wait for process to change state
SYNOPSIS
#include
#include
pid_t waitpid(pid_t pid, int *status, int options);
pid
> 0:waitpid
将等待pid为 pid
的进程status
:以位图方式存储子进程退出信息(输出型参数)options
:等待的方式
子进程退出无非就三种情况:
那也意味着status
需要表示这三种情况。那status
的位图结构是怎么回事?
对于status,我们只需要用到低16位,而不同的情况对应不同的存储意义:
怎么获取呢?可以通过一些位操作:
//获取终止信号:0~6位(第七位是core dump标志,暂时不关心)
//0x7F = 0111 1111b(相与得到0~6位)
int signal = status & 0x7F;
//获取进程退出码:8~15位
//(8~15) >> 8 = 0~7位
//0xFF = 1111 1111(相与得到0~7位)
int exit_code = (status >> 8) & 0xFF;
终止信号我们也接触过(kill -9),我们可以再看看有哪些:
[bacon@VM-12-5-centos wait1]$ 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
至于core dump,后面再谈。
了解各个参数的含义,现在来用一下。
代码正常跑完
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("child process: pid=%d ppid=%d; on:%d\n", getpid(), getppid(), cnt--);
sleep(1);
}
printf("child process exit now!\n");
exit(233);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(id > 0)
printf("wait success: pid=%d | exit code=%d | signal=%d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
sleep(5);
return 0;
}
[bacon@VM-12-5-centos wait1]$ ./test
child process: pid=22125 ppid=22124; on:5
child process: pid=22125 ppid=22124; on:4
child process: pid=22125 ppid=22124; on:3
child process: pid=22125 ppid=22124; on:2
child process: pid=22125 ppid=22124; on:1
child process exit now!
wait success: pid=22125 | exit code=233 | signal=0
代码出异常终止
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("child process: pid=%d ppid=%d; on:%d\n", getpid(), getppid(), cnt--);
sleep(1);
int a = 5;
a /= 0; //异常终止
}
printf("child process exit now!\n");
exit(233);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(id > 0)
printf("wait success: pid=%d | exit code=%d | signal=%d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
sleep(5);
return 0;
}
[bacon@VM-12-5-centos wait1]$ ./test
child process: pid=23423 ppid=23422; on:5
wait success: pid=23423 | exit code=0 | signal=8
低七位(0~6)存储 终止信号:8
对应kill -l
中的信号,8号代表浮点数错误,没毛病。
总说获取子进程退出信息,那
struct task_struct {
...
/* task state */
int exit_state;
int exit_code, exit_signal;
int pdeath_signal; /* The signal sent when the parent dies */
...
}
保存在子进程的task_struct
对象中。此时,如果父进程想获取这些信息,waitpid(id, &status, 0);
,操作系统就会去子进程的task_struct
对象中拿到信息,放进status
内。
(wait
/waitpid
是系统调用,由系统执行,它有资格也有能力读取子进程的task_struct
对象)
所以,等待的本质就是:从子进程的task_struct
对象中获取信息,放到status
。
用一趟下来,总觉得要用位操作获取信息太麻烦。是的,大佬也觉得,所以有几个宏可以用:
WIFEXITED(status)
WEXITSTATUS(status)
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("child process: pid=%d ppid=%d; on:%d\n", getpid(), getppid(), cnt--);
sleep(1);
}
printf("child process exit now!\n");
exit(233);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
if(WIFEXITED(status))
{
printf("exit code = %d\n", WEXITSTATUS(status));
}
else
{
printf("error!");
//...
}
}
return 0;
}
[bacon@VM-12-5-centos wait1]$ ./test
child process: pid=10945 ppid=10944; on:5
child process: pid=10945 ppid=10944; on:4
child process: pid=10945 ppid=10944; on:3
child process: pid=10945 ppid=10944; on:2
child process: pid=10945 ppid=10944; on:1
child process exit now!
exit code = 233
看完了基本的进程等待,就来谈谈之前提到的“阻塞式等待”是什么。
有阻塞式等待,自然有非阻塞式等待。我们举个例子理解二者的区别。
张三找李四开黑:“来?”“我作业还有一点,等下。”
此时张三需要等待李四,有两种等法:
打电话就是系统调用,检测李四的状态是否可以开黑。
前者就是阻塞式等待,一直检测。
后者就是非阻塞式等待,隔一段时间检测一下。
其中,多次非阻塞等待叫作轮询。
waitpid
的最后一个参数,就能控制等待方式。0是阻塞等待,对于非阻塞也有宏。
WNOHANG
:非阻塞等待。
阻塞式等待我们前面已经看过,现在来看看非阻塞等待和轮询,同时还能进一步学习waitpid
的用法。
int main()
{
pid_t id = fork();
if(id == 0)
{
//child
int cnt = 5;
while(cnt)
{
printf("child process: pid=%d ppid=%d; on:%d\n", getpid(), getppid(), cnt--);
sleep(1);
}
printf("child process exit now!\n");
exit(233);
}
//parent
//轮询
int status = 0;
while(1)
{
pid_t ret = waitpid(id, &status, WNOHANG); //非阻塞等待
//waitpid调用失败
if(ret < 0) //等待失败
{
printf("wait call failed!\n");
break;
}
//waitpid调用成功
else if(ret == 0) //没有等待失败,仅检测到子进程没有退出
{
printf("waitpid call success, but child process is still running...\n");
}
//waitpid调用成功
else //ret == id:成功等待到pid为id的子进程
{
printf("wait success: exit code = %d | exit signal = %d\n", WEXITSTATUS(status), status & 0x7F);
break;
}
sleep(1);
}
return 0;
}
[bacon@VM-12-5-centos poll]$ ./test
waitpid call success, but child process is still running...
child process: pid=11013 ppid=11012; on:5
waitpid call success, but child process is still running...
child process: pid=11013 ppid=11012; on:4
child process: pid=11013 ppid=11012; on:3
waitpid call success, but child process is still running...
waitpid call success, but child process is still running...
child process: pid=11013 ppid=11012; on:2
waitpid call success, but child process is still running...
child process: pid=11013 ppid=11012; on:1
waitpid call success, but child process is still running...
child process exit now!
wait success: exit code = 233 | exit signal = 0
但有个问题,阻塞等待不够用吗,非阻塞等待有啥用?
父进程可以干自己的事。
#include
#include
#include
#include
#include
#include
#include
#define NUM 10
typedef void (*func_t)(); //func_t是无参,返回值为空的函数指针
func_t task2Solve[NUM]; //函数指针数组,存放解决的任务
//样例任务
void task1()
{
printf("solving task1\n");
}
void task2()
{
printf("solving task2\n");
}
void task3()
{
printf("solving task3\n");
}
void loadTask()
{
memset(task2Solve, 0, sizeof(task2Solve));
task2Solve[0] = task1;
task2Solve[1] = task2;
task2Solve[2] = task3;
}
int main()
{
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
//child
int cnt = 5;
while(cnt)
{
printf("child process: pid=%d ppid=%d; on:%d\n", getpid(), getppid(), cnt--);
sleep(1);
}
printf("child process exit now!\n");
exit(233);
}
//parent
loadTask();
int status = 0;
while(1)
{
pid_t ret = waitpid(id, &status, WNOHANG); //非阻塞等待
if(ret < 0)
{
printf("wait call failed!\n");
break;
}
else if(ret == 0)
{
printf("waitpid call success, but child process is still running...\n");
for(int i = 0; task2Solve[i] != NULL; ++i)
{
task2Solve[i](); //回调函数:检测子进程没退出,父进程干自己的事
}
}
else
{
printf("wait success: exit code = %d | exit signal = %d\n", WEXITSTATUS(status), status & 0x7F);
break;
}
sleep(1);
}
return 0;
}
[bacon@VM-12-5-centos poll]$ ./test
waitpid call success, but child process is still running...
solving task1
solving task2
solving task3
child process: pid=14865 ppid=14864; on:5
waitpid call success, but child process is still running...
solving task1
solving task2
solving task3
child process: pid=14865 ppid=14864; on:4
child process: pid=14865 ppid=14864; on:3
waitpid call success, but child process is still running...
solving task1
solving task2
solving task3
waitpid call success, but child process is still running...
solving task1
solving task2
solving task3
child process: pid=14865 ppid=14864; on:2
waitpid call success, but child process is still running...
solving task1
child process: pid=14865 ppid=14864; on:1
solving task2
solving task3
waitpid call success, but child process is still running...
child process exit now!
solving task1
solving task2
solving task3
wait success: exit code = 233 | exit signal = 0
学习程序替换,首先要回答一个问题:
其中,“创建子进程,让子进程执行另外的程序”,就是进程程序替换。
六个程序替换函数:
NAME
execl, execlp, execle, execv, execvp, execvpe - execute a file
SYNOPSIS
#include
extern char **environ;
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[]);
挑个简单的execl
用:
int execl(const char* path, const char* arg, ...)
作用:将指定程序加载到内存,让指定进程执行
你让我执行一个程序,肯定要告诉我程序在哪里,然后告诉我怎么执行
参数
path
:要加载的文件arg
:命令行参数...
:可变参数列表#include
#include
int main()
{
printf("process running...\n");
execl("/usr/bin/ls", //要执行谁
"ls", "--color=auto", "-a", "-l", NULL); //怎么执行
//所有exec的函数传参都以NULL结尾
printf("process running done!\n");
return 0;
}
[bacon@VM-12-5-centos substitution]$ ./test
process running...
total 28
drwxrwxr-x 2 bacon bacon 4096 Jan 17 07:57 .
drwxrwxr-x 11 bacon bacon 4096 Jan 17 07:48 ..
-rw-rw-r-- 1 bacon bacon 74 Jan 17 07:48 makefile
-rwxrwxr-x 1 bacon bacon 8408 Jan 17 07:57 test
-rw-rw-r-- 1 bacon bacon 390 Jan 17 07:57 test.c
诶?最后一句printf
怎么没执行?这就需要了解程序替换的原理了
程序替换的本质:将指定程序的代码和数据直接加载到指定位置(覆盖指定位置的数据)。
没有,仅仅是将一些数据和代码覆盖式加载到指定位置。
这也就能解释为什么最后一句printf
没执行了:进程的数据和代码已经被新的程序替换了。
如果execl
调用失败,没将新的代码和数据覆盖加载,最后一句printf
还在,也就会正常打印。
int main()
{
printf("process running...\n");
execl("/usr/bin/lsfdsajidofjio", //传参错误
"ls", "--color=auto", "-a", "-l", NULL);
printf("process running done!\n");
return 0;
}
[bacon@VM-12-5-centos substitution]$ ./test
process running...
process running done!
execl
只有在出错的时候返回1,我们看看是不是调用出错了。
int main()
{
//.c ==> exe ==> load ==> process ==> run ==> execute code
printf("process running...\n");
//load ==> execute code
int ret = execl("/usr/bin/lsfdsajidofjio", //要执行谁
"ls", "--color=auto", "-a", "-l", NULL); //怎么执行
//所有exec的函数传参都以NULL结尾
printf("ret = %d\n", ret);
printf("process running done!\n");
return 0;
}
[bacon@VM-12-5-centos substitution]$ ./test
process running...
ret = -1
process running done!
结果符合预期。
为什么成功不返回呢?因为一旦替换成功,返回也没有意义了,因为原来的代码都没了,也不会再用这个返回值。
看一段代码:
int main()
{
//.c ==> exe ==> load ==> process ==> run ==> execute code
printf("process running...\n");
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
sleep(1);
//这里的替换,会不会影响父进程?
execl("/usr/bin/ls", "ls", "-a", "-l", "--color=auto", NULL);
exit(1); //因为替换成功,这里的代码就被覆盖,不执行。所以执行到这里一定是调用失败返回了
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret == id) printf("wait success: exit code = %d | sig = %d\n", ((status)>>8) & 0xFF, status & 0x7F);
// //load ==> execute code
// int ret = execl("/usr/bin/lsfdsajidofjio", //要执行谁
// "ls", "--color=auto", "-a", "-l", NULL); //怎么执行
// //所有exec的函数传参都以NULL结尾
// printf("ret = %d\n", ret);
//
printf("process running done!\n");
return 0;
}
不会,因为进程具有独立性。
当要写入(把指定程序载入)时,父子进程原本共享的代码就必须进行写时拷贝,再拷贝一份数据和代码。此时子进程的替换,根本不影响父进程!
exec
系列函数的使用NAME
execl, execlp, execle, execv, execvp, execvpe - execute a file
SYNOPSIS
#include
extern char **environ;
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
列表方式传arg
参数(一个一个传)
p
:以文件名的方式传file
参数(自动在PATH找)e
:传环境变量数组envp
v
:以vector
数组方式传传argv
参数(直接传一个数组)
p
:以文件名的方式传file
参数(自动在PATH找)e
:传环境变量数组envp
在学习之前,来试试将我们的程序替换进一个进程中。
首先,makefile
默认只生成一个可执行,所以可以这样:
.PHONY:all
all: test bin
test:test.c
gcc -o test -std=c99 test.c
gcc -o bin -std=c99 bin.c
.PHONY:clean
clean:
rm -f test
rm -f bin
execl("./bin", "./bin", NULL);
[bacon@VM-12-5-centos substitution]$ ./test
process running...
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
wait success: exit code = 0 | sig = 0
process running done!
int execlp(const char *file, const char *arg, ...);
其他代码同execl
使用一样,仅改变exec
函数的使用。
execlp("ls", "ls", "-a", "-l", "--color=auto", NULL);
int execle(const char *path, const char *arg, ..., char * const envp[]);
1.传自定义环境变量(不要系统的)
//test.c
int main()
{
printf("process running...\n");
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
sleep(1);
char* const myenvp[] = { "MYENV=8848", NULL };
execle("./bin", "bin", NULL, myenvp);
exit(1);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret == id) printf("wait success: exit code = %d | sig = %d\n", ((status)>>8) & 0xFF, status & 0x7F);
printf("process running done!\n");
return 0;
}
//bin.c
int main()
{
//系统
printf("PATH=%s\n", getenv("PATH"));
printf("PWD=%s\n", getenv("PWD"));
//自定
printf("MYENV=%s\n", getenv("MYENV"));
printf("This is the other .exe!\n");
printf("This is the other .exe!\n");
printf("This is the other .exe!\n");
printf("This is the other .exe!\n");
printf("This is the other .exe!\n");
printf("This is the other .exe!\n");
printf("This is the other .exe!\n");
printf("This is the other .exe!\n");
printf("This is the other .exe!\n");
printf("This is the other .exe!\n");
return 0;
}
[bacon@VM-12-5-centos substitution]$ ./test
process running...
PATH=(null)
PWD=(null)
MYENV=8848
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
wait success: exit code = 0 | sig = 0
process running done
传了自定义环境变量,所以系统的获取不到。
2.传系统的
int main()
{
printf("process running...\n");
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
sleep(1);
extern char** environ;
char* const myenvp[] = { "MYENV=8848", NULL };
execle("./bin", "bin", NULL, environ);
exit(1);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret == id) printf("wait success: exit code = %d | sig = %d\n", ((status)>>8) & 0xFF, status & 0x7F);
printf("process running done!\n");
return 0;
}
[bacon@VM-12-5-centos substitution]$ ./test
process running...
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/bacon/.local/bin:/home/bacon/bin
PWD=/home/bacon/linux/5-process_control/substitution
MYENV=(null)
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
wait success: exit code = 0 | sig = 0
process running done!
传系统的,自定义的就获取不到了。
但实际上,系统的环境变量我们就算不传,也会从父进程继承。怎么继承的?
还记得进程地址空间有一部分是环境变量,为子进程创建地址空间的时候就会拷贝。
那我既想要系统的,又想要自定的,怎么办?
int puenv(char* string)
int main()
{
printf("process running...\n");
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
sleep(1);
extern char** environ;
putenv((char*)"MYENV=8848"); //将指定环境变量导入到environ指向的环境变量表中
execle("./bin", "bin", NULL, environ);
exit(1);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret == id) printf("wait success: exit code = %d | sig = %d\n", ((status)>>8) & 0xFF, status & 0x7F);
printf("process running done!\n");
return 0;
}
[bacon@VM-12-5-centos substitution]$ ./test
process running...
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/bacon/.local/bin:/home/bacon/bin
PWD=/home/bacon/linux/5-process_control/substitution
MYENV=8848
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
This is the other .exe!
wait success: exit code = 0 | sig = 0
process running done!
将指定的自定义环境变量导入到environ指向的环境变量表中,在程序替换时选择传入系统的环境变量。自定的和系统的都能获取到了。
int execv(const char *path, char *const argv[]);
char* const myargv[] = {
"ls",
"-a",
"-l",
"--color=auto",
NULL
};
execv("/usr/bin/ls", myargv);
int execvp(const char *file, char *const argv[]);
…
int execvpe(const char *file, char *const argv[],char *const envp[]);
…
还有一个系统调用,以上函数都是通过这个系统调用封装来的。
int execve(const char *filename, char *const argv[], char *const envp[]);
NAME
execve - execute program
SYNOPSIS
#include
int execve(const char *filename, char *const argv[],
char *const envp[]);
Linux的exec系列函数,也叫做加载器。
main函数也是通过加载器加载。
int main(int argc, char* argv[], char* env)
int execle(const char* path, const char* arg, ..., char* const envp[]);
argc
和 argv
通过 arg
获取,env
通过envp
获取。
shell
简易地实现一个shell
makefile
myshell:myshell.c
gcc -o $@ $^ -std=c99 #-DDEBUG #DDEBUG:定义宏——DEBUG
.PHONY:clean
clean:
rm -f myshell
myshell.c
#include
#include
#include
#include
#include
#include
#include
#define NUM 1024
#define OPT_NUM 32
char lineCommand[NUM];
char* myargv[OPT_NUM];
int main()
{
while(1)
{
//1. 提示符
printf("[%s@%s]# ", getenv("USER"), getenv("HOSTNAME"));
fflush(stdout);
//2. 获取输入的命令(排除用户输入的\n)
char* ret = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
assert(ret != NULL);
lineCommand[strlen(lineCommand) - 1] = 0; //排除用户\n
//*测试获取命令
//printf("test: %s\n", lineCommand);
//3. 命令解析(字符串切割)
//"ls -a -l" ==> "ls" "-a" "-l"
myargv[0] = strtok(lineCommand, " ");
//没有子串后,strtok返回NULL,正巧myargv需要以NULL结尾,所以...
int i = 1;
while(myargv[i++] = strtok(NULL, " "));
#ifdef DEBUG
//*测试命令解析
for(int j = 0; myargv[j]; ++j) printf("myargv[%d] = %s\n", j, myargv[j]);
#endif
//4. 执行命令
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
execvp(myargv[0], myargv);
exit(1);
}
waitpid(id, NULL, 0);
}
return 0;
}
[bacon@VM-12-5-centos myshell]$ ./myshell
[bacon@VM-12-5-centos]# ls
makefile myshell myshell.c
[bacon@VM-12-5-centos]# pwd
/home/bacon/linux/5-process_control/myshell
主要就四个部分:
基本也能跑了,但有点bug:
[bacon@VM-12-5-centos]# pwd
/home/bacon/linux/5-process_control/myshell
[bacon@VM-12-5-centos]# cd ..
[bacon@VM-12-5-centos]# pwd
/home/bacon/linux/5-process_control/myshell
[bacon@VM-12-5-centos]# cd ..
[bacon@VM-12-5-centos]# pwd
/home/bacon/linux/5-process_control/myshell
这是什么原因?想知道,首先得了解pwd
想获取的当前工作目录cwd
。
cwd(current working directory)
是某进程当前的工作目录,exe
就是可执行程序在磁盘上的位置。
既然cwd
是属于进程的,不同进程的cwd
自然互相独立。
我们的shell执行命令时采用“子进程内程序替换”的方式。
cd ..
改变子进程的cwd
(从父进程继承,又互相独立)cwd
也随之去了cwd
还是没变,白忙活了。知道了原因(子进程cwd不影响父进程cwd),怎么解决?
可以通过“内建命令”的方式。什么叫内建命令?可以理解为在当前进程执行,不创建子进程执行。
#include
#include
#include
#include
#include
#include
#include
#define NUM 1024
#define OPT_NUM 32
char lineCommand[NUM];
char* myargv[OPT_NUM];
int main()
{
while(1)
{
//提示符
printf("[%s@%s]# ", getenv("USER"), getenv("HOSTNAME"));
fflush(stdout);
//获取输入的命令(排除用户输入的\n)
char* ret = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
assert(ret != NULL);
lineCommand[strlen(lineCommand) - 1] = 0; //排除用户\n
//测试获取命令
//printf("test: %s\n", lineCommand);
//命令解析(字符串切割)
//"ls -a -l" ==> "ls" "-a" "-l"
myargv[0] = strtok(lineCommand, " ");
//没有子串后,strtok返回NULL,正巧myargv需要以NULL结尾,所以...
int i = 1;
while(myargv[i++] = strtok(NULL, " ")); //最后一次先赋NULL给myargv[i],再判断为假跳出
//内建命令
if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
{
if(myargv[1] != NULL) chdir(myargv[1]);
continue;
}
#ifdef DEBUG
//测试命令解析
for(int j = 0; myargv[j]; ++j) printf("myargv[%d] = %s\n", j, myargv[j]);
#endif
//执行命令
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
execvp(myargv[0], myargv);
exit(1);
}
waitpid(id, NULL, 0);
}
return 0;
}
[bacon@VM-12-5-centos myshell]$ ./myshell
[bacon@VM-12-5-centos]# pwd
/home/bacon/linux/5-process_control/myshell
[bacon@VM-12-5-centos]# cd ..
[bacon@VM-12-5-centos]# pwd
/home/bacon/linux/5-process_control
[bacon@VM-12-5-centos]# cd ..
[bacon@VM-12-5-centos]# pwd
/home/bacon/linux
还可以再实现一个内建命令echo
。
#include
#include
#include
#include
#include
#include
#include
#define NUM 1024
#define OPT_NUM 32
char lineCommand[NUM];
char* myargv[OPT_NUM];
int lastCode = 0;
int lastSig = 0;
int main()
{
while(1)
{
//提示符
printf("[%s@%s]# ", getenv("USER"), getenv("HOSTNAME"));
fflush(stdout);
//获取输入的命令(排除用户输入的\n)
char* ret = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
assert(ret != NULL);
lineCommand[strlen(lineCommand) - 1] = 0; //排除用户\n
//测试获取命令
//printf("test: %s\n", lineCommand);
//命令解析(字符串切割)
//"ls -a -l" ==> "ls" "-a" "-l"
myargv[0] = strtok(lineCommand, " ");
//没有子串后,strtok返回NULL,正巧myargv需要以NULL结尾,所以...
int i = 1;
while(myargv[i++] = strtok(NULL, " "));
//内建命令
if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
{
if(myargv[1] != NULL) chdir(myargv[1]);
continue;
}
if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
{
//"$?" 实际上是获取环境变量的值,这里只是演示,就硬判断了
if(strcmp(myargv[1], "$?") == 0)
{
printf("exitCode = %d | exitSig = %d\n", lastCode, lastSig);
}
else
{
printf("%s\n", myargv[1]);
}
continue;
}
#ifdef DEBUG
//测试命令解析
for(int j = 0; myargv[j]; ++j) printf("myargv[%d] = %s\n", j, myargv[j]);
#endif
//执行命令
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
execvp(myargv[0], myargv);
exit(1);
}
int status = 0;
pid_t wait_ret = waitpid(id, &status, 0);
assert(wait_ret > 0);
lastCode = (status >> 8) & 0xFF;
lastSig = status & 0x7F;
}
return 0;
}
到这里我们的shell就差不多了,主要目的是把之前的知识串起来用用。
今天的分享就到这里了
这里是培根的blog,期待与你共同进步
下期见