Linux系统编程:进程管理、进程结束与exec函数全解析
本文摘要: 文章系统讲解了Linux进程管理的核心概念,包括:1)进程与程序的区别(动态/静态);2)进程状态、组成(PCB+内存段);3)进程创建(fork)及特殊状态(孤儿/僵尸进程);4)进程执行(exec函数族)的6种方式及环境变量处理;5)进程终止(exit/_exit)和资源回收(wait);6)实用函数(strtok字符串分割)和案例(实现简易shell)。重点剖析了进程生命周期中的
目录
1、进程
进程:程序的一次执行过程
进程:动态 保存在内存
程序:静态 保存在硬盘
一个程序可以对应多个进程
2、进程的状态
3、进程的组成
PCB+堆|栈|bss|data|text
4、进程的创建fork()
复制调用进程,创建子进程

练习:父子进程的拷贝
5、进程结束的两个特殊状态
孤儿进程
子进程还在,但是父进程结束了------------此时子进程会成为孤儿进程------------会成为后台进程,此时ctrl+c无法停止,因为ctrl+c本质是一个发信号,一般是发给前台进程的

当一个进程成为孤儿进程后,会被系统的systemd(早期为init(1)进程)进程收养。因为子进程最终的资源一定要有人回收,如果没有人回收就会出现僵尸态。
僵尸态
子进程结束了,父进程没有做收尸操作(资源回收),那么子进程的PCB块就一直存在。
僵尸态的进程是有危害的,一般需要做处理-----------做收尸操作(回收资源)
stat函数
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
功能
- 获得文件状态信息
参数
- @pathname:文件名
- @ststbuf:文件状态信息的结构体
返回值
- 成功返回0
- 失败返回-1而且errno会被设置
fork之后文件共享的问题
1、如果是fork之前打开文件,子进程会继承父进程中已经打开的文件描述符;此时父子进程各有各的文字描述符,但是他们共享了文件表项(文件状态和文件偏移量),此时,操作文件会相互影响偏移量。
2、如果不想相互影响,可以将打开文件的操作放在fork之后
6、进程的执行
与父进程做独立的事
fork+exec
exec函数
- int execl(const char *path, const char *arg, .../* (char *) NULL */);
- int execlp(const char *file, const char *arg, .../* (char *) NULL */);
- int execle(const char *path, const char *arg, .../*, (char *) NULL, 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[]);
exec l / exec v
- int execl(const char *path, const char *arg, .../* (char *) NULL */);
- int execv(const char *path, char *const argv[]); (紫色部分为指针数组)
l------------------------list:表示从第二个参数开始,传参时把参数逐个列举出来
- execl(“/bin/ls”,“ls”,“-l”,“.”,NULL)
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
printf("exec start\n");
execl("/bin/ls","ls",".",NULL);
printf("exec end\n");
return 0;
}
v------------------------vector:向量(数组),表示传参时,将要传的参数组织成指针数组的方式
- char *const args[]={"ls","-l",".",NULL};
- execv("/bin/ls",args);
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
printf("exec start\n");
char *const args[]={"ls","-l",".",NULL};
execv("/bin/ls",args);
printf("exec end\n");
return 0;
}
功能

镜像(进程的组成部分)
exec函数 用新的进程的各个段 替换了 当前进程的各个段,但是PCB还是之前的PCB
参数
@path:表示要执行的新程序的路径。eg:"/bin/ls"
@arg:代表要执行的文件的名字
返回值
- 成功返回0
- 失败返回-1而且erno会被设置
查看cp在哪里:which cp
exec l p / exec v p
p--------------------------PATH 系统的环境变量--------------------------echo $PATH
- int execlp(const char *file, const char *arg, .../* (char *) NULL */);
- int execvp(const char *file, char *const argv[]);
参数
file:表示可执行的文件(名字)
这个文件不需要指定路径,当程序运行时,默认会到path环境变量中寻找
PATH-----------系统的 环境变量-------用来记录当前系统的各个二进制文件(可执行文件)的路径
execlp
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { printf("exec start\n"); if(execlp("ls","ls","-l",".",NULL)<0) { perror("execlp fail"); return -1; } printf("exec end\n"); return 0; }
execvp
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { printf("exec start\n"); char *const args[]={"ls","-l",".",NULL}; execvp("ls",args); printf("exec end\n"); return 0; }
exec l p e / exec v p e
- int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
- int execvpe(const char *file, char *const argv[],char *const envp[]);
带e,表示environment----------------环境变量
extern char **environ; 表示environ变量定义在外部,如果要用请到外部查找。是系统中定义好的环境变量指针。
PATH = /bin,系统的环境变量都是这种形式 键值对,key=value
带e的exec函数主要的用途可以给要运行的程序传递环境变量
eg:env 这个程序用到了环境变量-------打印所有环境变量
execlpe
#include <stdio.h> #include <unistd.h> extern char **environ; int main(int argc, const char *argv[]) { char *ev[]={"USER=hello","PASSWORD=12345",NULL}; //系统 execle("/usr/bin/env","env",NULL,environ);//表示要运行的这个env使用时系统的环境变量 //自己组织的环境变量 //execle("/usr/bin/env","env",NULL,ev);//表示要运行的这个env使用时系统的环境变量 return 0; }
execvpe
这个编译的时候 要加 -D _GNU_SOURCE
#include <stdio.h> #include <unistd.h> extern char **environ; int main(int argc, const char *argv[]) { char *ev[]={"USER=hello","PASSWORD=12345",NULL}; char *const args[]={"env",NULL}; //execvpe("env",args,environ); execvpe("env",args,ev); return 0; }
strtok函数
#include <string.h>
char *strtok(char* str,const char delim)
功能
- 拆分字符串
参数
- @str:要拆分的字符串
- @delim:表示拆分的标志,也是字符串形式
返回值
- 成功返回 拆好的字符串 的 首地址
- 失败返回NULL
注意
- 如果想接着之前的字符串往下拆分,后续调用时,第一个参数必须写成NULL,strtok(NULL,";:")
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char buf[10]="ls;-l:.";
char* s1=strtok(buf,";:");//遇到:;分割
printf("s1:%s\n",s1);
char* s2=strtok(NULL,";:");
printf("s2:%s\n",s2);
char* s3=strtok(NULL,";:");
printf("s3:%s\n",s3);
char buf1[10]="cp/-l,?";
char* s4=strtok(buf1,"/,");//遇到/,分割
printf("s4:%s\n",s4);
char* s5=strtok(NULL,"/,");
printf("s5:%s\n",s5);
char* s6=strtok(NULL,"/,");
printf("s6:%s\n",s6);
return 0;
}
实现ls
#include <stdio.h> #include <unistd.h> #include <string.h> int main(int argc, const char *argv[]) { //读入字符串 char buf[100]={0}; printf("myshell$ "); while(1) { fgets(buf,sizeof(buf),stdin); buf[strlen(buf)-1] = '\0'; //判断 if (strcmp(buf,"quit")==0 ||(strcmp(buf,"exit"))==0) { printf("程序停止\n"); return -1; } else { //strtok分割 char *s[10]={NULL}; int i=0; s[i++] = strtok(buf," "); while(s[i++] = strtok(NULL," ")) ; //fork创建子进程 pid_t pid=fork(); if(pid<0) { perror("fork fail"); return -1; } #if 1 //父进程 if(pid>0) { sleep(1); } #endif //子进程 if(0==pid) { if(execvp(s[0],s)<0) { perror("exec fali"); return -1; } } } } return 0; }
7、进程的结束
(1)正常结束
- 从main函数返回
- exit函数 ---------------正常结束
- _exit函数 --------------正常结束

exit函数
#include <stdlib.h>
void exit(int status);
功能
- 造成进程正常结束
参数
- @status:是传递给父进程的 状态信息,status & 0377 --------只有最低8位有效
库函数
- 退出之前会刷新IO缓存
- 调用退出处理函数
atexit函数(注册函数)
#include <stdlib.h>
int atexit(void (*function)(void));
功能
- 用来注册一个退出清理函数
参数
- function:函数指针,指向一个函数的------调用函数
返回值
- 成功返回0
- 失败返回非0值
注意
void clean_up(void) 退出清理函数
{
}
多次注册时,注册顺序和调用顺序是反着的 //栈

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
char* p=NULL;
void clean_up1(void)
{
printf("clean_up 1--------\n");
free(p);
}
void clean_up2(void)
{
printf("clean_up 2--------\n");
free(p);
}
int main(int argc, const char *argv[])
{
atexit(clean_up1);
atexit(clean_up2);
p=malloc(10);
strcpy(p,"hello!");
printf("p:%s\n",p);
sleep(3);
exit(0);
return 0;
}
_exit函数
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);
功能
- 立即结束调用进程,不会有 处理IO缓存 和 退出清理函数。
exit和_exit的区别
第一个执行是exit,会刷新缓存和调用处理函数
第二个是_exit,不会刷新缓存

(2)异常结束
- abort函数:中止 ------------ 异常结束
- signal:发信号结束的进程
8、进程资源的回收
(1)wait
#include <sys/wait.h>
pid_t wait(int *wstatus);
功能
- 等待子进程状态改变,但是这里我们研究的是子进程的退出
参数
- @wstatus:用来保存子进程退出的状态值的
返回值
- 成功返回 结束了的子进程的PID
- 失败返回 -1
更多推荐




所有评论(0)