程序退出的场景:
1.代码运行完毕,结果正确
2.代码运行完毕,结果不正确
3.代码异常终止
常见的进程退出方法
正常终止:(可通过echo $? 查看进程的退出码):
1.main函数的return
执行return n; 等同于执行exit(n);因为调用main的运行时函数会将main的返回值当做exit的参数
2.调用exit
1>调用退出处理程序(用户通过atexit或on_exit定义的清理函数)
2>刷新输出缓存(关闭所有打开的流, 所有缓存数据均被写入)
3>调用_exit
3._exit (n) : 不执行退出程序
void _exit(int status); status定义了进程的终止状态,父进程通过wait来获取该值
虽然status是int型,但是仅有低8位可以被父进程所用。所以_exit(-1)在终端下执行echo $?会发现返回255
异常退出:
ctrl + c
abort();
进程等待:
wait: pid_t wait(int *status);
一直阻塞,直到有一个子进程死亡,成为僵尸进程,回收掉僵尸进程,并且wait返回,pid_t wait(int *status),status获得子进程的死亡信息。
返回值: 成功返回被等待进城的pid,失败返回-1
参数: 输出型参数,获取子进程的退出状态,不关心则可以设置为NULL
wait中几个重要的函数:
WIFEXITED(status)返回真表示子进程正常退出
WEXITSTATUS(status) 得到子进程的退出码
WIFSIGNALED(status) 返回真, 被信号打断
WTERMSIG(status) 获得信号的信息
kill -num pid 给pid进程发送num信号,缺省为15
#include
#include
#include
#include
int main( void )
{
pid_t pid = fork();
if (pid == -1)
{
perror("fork"),
exit(1);
}
if ( pid == 0)
{
int i;
for (i=0; i < 10; i++)
{
printf("$");
fflush(stdout);
sleep(1);
}
exit(10);
}
else
{
int status;
int ret = wait(&status); //清理子进程
if(ret > 0 && (status & 0x7F) == 0)
{
printf("child exit code : %d\n", (status>>8) & 0xFF);
}
else if(ret > 0)
{
printf("sig code : %d\n", status & 0x7F);
}
}
}
waitpid: pid_t waitpid(pid_t pid, int *status, int options);
返回值:
1.当正常返回时,waitpid返回收集到的子进程的id。
2.如果设置options为WNOHANG,而调用中waitpid发现没有已退出的子进程可手机则返回0;
3.如果调用中出错,则返回-1,这时errno会被设置为相应的值来指示错误的所在。
参数:
pid:
pid > 0: 等待指定进程
pid == 0: 等待本进程组的任何一个子进程死亡
pid = -1: 等待本进程的任何一个子进程死亡
pid < -1: 等待|pid|进程组的任何一个子进程死亡
status:
WIFEXITED(status) 返回真表示子进程正常退出
WEXITSTATUS(status) 非零,得到子进程的退出码
options:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待。若正常结束,则返回盖子进程的ID。
补充:
如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并释放资源,获得子进程退出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则可能阻塞。
如果不存在该子进程,则立即出错返回。
获取子进程status:
status为输出型参数,有操作系统填充。如果传递NULL,表示不关心子进程的退出状态信息。否则系统根据该参数,将子进程的退出信息反馈给父进程。
进程正常终止时,status 8-15位存放退出状态,当被信号所结束时,0-6位存放终止信号,第7位存放core dump标志
#include
#include
#include
#include
int main( void )
{
pid_t pid;
if ((pid=fork()) == -1) perror("fork"),exit(1);
if ( pid == 0 ) {
int i;
for (i=0; i<3; i++) {
printf("$");
fflush(stdout);
sleep(1);
}
exit(-1);
} else {
int status;
pid_t p = waitpid(pid, &status, WNOHANG); //非阻塞式等待
if ( WIFEXITED(status) ) {
printf("exit code=%u\n", WEXITSTATUS(status));
}
if ( WIFSIGNALED(status) ) {
printf("exit sig=%u\n", WTERMSIG(status));
}
for ( ; ; ) {
printf(".");
fflush(stdout);
sleep(1);
}
}
}
进程的替换:
用fork()创建子进程后执行的是和父进程相同的程序,子进程往往要调用一种exec函数以执行另一个程序,当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新进程的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
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[]);
函数解释:
1.这些函数如果调用成功,则加载新的程序从代码开始执行,不再返回。
2.如果调用出错则返回-1
3.所以exec函数只有出错的返回值而没有成功的返回值。
l(list): 表示参数使用列表
v(vector): 参数使用数组
p(path): 自动搜索环境变量PATH
e(env): 表示自己维护环境变量
事实上,只有execve是真正的系统调用,其他五个函数最终都调用execve。
execvp:
#include
#include
#include
int main()
{
printf("exec before:\n");
char *const argv[] = {
"ls",
"-l",
NULL
};
if( execvp("/bin/ls", argv) == -1)
{
perror("execvp\n");
};
printf("exec after:\n");
return 0;
}
所有的exec族函数
#include
#include
#include
int main()
{
// execl("/bin/ls", "ls", "-l", NULL); //路径,argv[]
// execl("ls", "ls", "-l", NULL); //err;
//execlp("ls", "ls", "-l", NULL); //自动检查路径, argv[0], argv[1]-看做占位符
// char *argv[] = {"ls", "-l", NULL};
// execv("/bin/ls", argv); //带e的需要自己组装环境变量
execvp("ls", argv); //无需写全路径
char *argv[] = {"myenv", NULL};
char *envp[] = {"aa=bb", "cc=dd", "ee=ff", NULL};
execve("./myenv", argv, envp); //需自己组装环境变量
perror("execl");
}
利用以上知识就可以实现一个简单的系统命令的调用,和简单的shell
#include
#include
#include
#include
#include
#include
int my_system(const char* str)
{
if(NULL == str)
{
return 1;
}
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
return -1;
}
if(0 == pid)
{
execlp("/bin/sh", "sh", "-c", str, NULL);
exit(127);
}
int status;
while(-1 == waitpid(pid, &status, 0)) //获取等待码
{
if(errno == EINTR) //信号打断
{
continue;
}
return -1;
}
return WEXITSTATUS(status);
}
int main()
{
my_system("clear");
}
简易的shell:
思路:需要循环的进行,获取命令,解析命令,建立一个子进程(fork()),替换一个子进程(execvp()),父进程等待子进程退出(wait())。
#include
#include
#include
#include
#include
void do_shell(int argc, char *argv[])
{
pid_t pid = fork();
if(pid == 0){
if(execvp(argv[0], argv) == -1) //如果替换成功会自动退出,失败打印错误
{
perror("execvp");
exit(1); //调用失败退出
}
}else if(pid == -1)
{
perror("fork"); //子进程创建失败
exit(1);
}
else{
wait(NULL); //清理子进程
}
}
void do_parse(char *buf){
int argc = 0;
char *argv[8];
int i = 0;
int status = 0;
for( i = 0; buf[i] != 0; ++i)
{
if(!isspace(buf[i]) && status == 0) //是字符
{
status = 1;
argv[argc++] = &buf[i];
}else if(isspace(buf[i]))
{
status = 0;
buf[i] = 0;
}
}
argv[argc] = NULL;
do_shell(argc, argv);
}
int main()
{
char buf[1024] = {};
while(1)
{
memset(buf, 0x00, sizeof buf);
printf("czf-shell:>");
scanf("%[^\n]%*c", buf);
if(strncmp(buf, "exit", 4) == 0){
exit(0);
}
do_parse(buf);
}
}