今天介绍进程的进程的管理,子进程创建以及进程资源的回收
首先什么是进程?
答:进程是程序的实例。程序是静态的,是存放在硬盘上的,程序运行起来就形成了进程。程序从磁盘到内存里之后就形成了进程。
进程又分为:用户级进程和内核级进程
我们下面了解用户级进程:
操作系统为了和管理进程,会有一个控制块来记录进程用到了哪些资源。叫做PCB块。
每一个进程都有自己的一个ID叫做PID。
用以下命令可以查看当前的进程
pstree 查看进程树 (init的PID为1)
ps -aux 查看当前的进程表
ps -o pid,ppid,..... 查看当前bash下的进程的信息(信息自己添加如PID PPID)
下面介绍一个创建子进程的函数fork()
#include<unistd.h>
pid_t fork (void)
功能:创建一个子进程
参数:无
返回值:成功时,在子进程中返回0,在父进程中返回子进程的PID (在A进程里创建了B子进程,A就是B的父进程)。
失败时返回- 1 errno被设置
子进程会继承父进程的PCB里的所有资源(他们除了PID不一样其余在你使用fork()函数之后的一切东西都一样);
介绍函数exit()
#include <stdlib.h>
void exit(int status);
功能:使进程正常终止参数:指定退出的值。将status&0377 的数值传递给父进程
在进程终止之后,还有一部分资源滞留在系统中,我们需要回收进程所占用的资源
使用wait()函数可以回收进程的资源
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:阻塞等待子进程的终止,子进程终止的时候回收子进程的资源,立即返回
参数:
status:用来存储子进程状态码的地址,如果为NULL ,不希望保留子进程的退出状态码,可以使用宏来检测退出状态
WIFEXITED(status):如果子进程正常终止,返回真
WEXITSTATUS(status):在上个宏返回真的情况下才能被使用.获取子进程的退出状态码的低8位
WIFSIGNALED(status):如果子进程是被一个信号终止的返回真
WTERMSIG(status):返回终止子进程的信号的编号,在上个宏为真的情况下使用
返回值:成功返回种植的子进程的PID,失败返回-1
僵尸进程:子进程已经终止但是父进程没有回收他的资源,这时候子进程就是僵尸进程状态
孤儿进程;:父进程结束了,子进程还没结束。这时候子进程就叫做孤儿进程。并且他会过继给INIT 1号进程(INIT当他的父进程)
创建的子进程和父进程默认是同组的。
下面代码演示:
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 #include<sys/wait.h>
6 int main(){
7 int a;
8 pid_t pid;
9 pid=fork();
10 if(pid==-1){
11 perror("fork");
12 return -1;
13 }
14 if(pid==0){//子进程
15 getchar();
16 exit(7);
17 }else{
18 wait(&a);
19 printf("回收成功!\n");
20 if(WIFEXITED(a))printf("son exit status=%d\n",WEXITSTATUS(a));
21 if(WIFSIGNALED(a))printf("kill signal num=%d\n",WTERMSIG(a));
22 }
23 return 0;
24 }
结果如下:
tarena@ubuntu:~/LIANXI/10.19$ gcc wait_a.c
tarena@ubuntu:~/LIANXI/10.19$ ./a.out
回收成功!
son exit status=7
在执行程序之后,由于在父进程中wait的存在,他会一直等子进程结束并回收其资源。当我们输入任意指令跳过子进程中的getchar时,子程序结束。
第21行代码:如果我们没有去跳过getchar 而是被一个信号中断了程序 如 kill -9 pid(子进程的pid,利用ps -aux 查看,或直接在程序中getpid)时,会打印终止我们程序的信号编号。
下面介绍在进程执行过程中,加载新的映像(可执行文件),来替换掉从父进程那里继承来的映像的函数:
函数: exec(3)系列函数
#include <unistd.h>
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[]);
功能:执行程序
参数:
共同点:都有exec
l:列表形式 const char *arg 和 char *const argv[] 的区别,有l的话需要将数组的每个成员都写在函数形式参数那里。
V:向量形式 只需要写数组的名字即可
都需要在函数的第二个参数后面加上NULL
p:如果有p就是const char *file (这里只需要填写要执行的可执行程序的名字,但是PATH环境变量里必须能找到这个可执行文件)没有就是const char *path 写的时候需要加上路径(比如./)
e:没有e就是子进程继承父进程的环境变量,有e就是给子进程传递新的环境变量
返回值:执行成功不返回,不成功返回-1
演示一个简单的,代码如下:
首先这个代码输出当前进程的环境变量:
1 #include<stdio.h>
2 extern char **environ;
3 int main(){
4 int i=0;
5 for(i=0;environ[i]!=NULL;i++){
6 printf("%s\n",environ[i]);
7 }
8 return 0;
9 }
下面这个代码演示把上面的程序替换为创建的子进程的程序
1 #include<stdio.h>
2 #include <unistd.h>
3 #include <unistd.h>
4 int main(){
5 pid_t pid=fork();
6 char *const envp[]={"caption=beijing","wo=cool",NULL};
7 if(pid==-1){
8 perror("fork");
9 return -1;
10 }
11 if(pid==0){
12 execle("./a","a",NULL,envp);
13 }else{
14 getchar();
15 }
16 return 0;
17 }
运行结果如下:
tarena@ubuntu:~/LIANXI/10.19$ gcc execl.c
tarena@ubuntu:~/LIANXI/10.19$ ./a.out
caption=beijing
wo=cool
tarena@ubuntu:~/LIANXI/10.19$
下面讲一下在当前bash下运行一个程序发生了什么?
首先什么是bash?
bash也是一个程序,是操作系统和用户交互的一个进程。
写在bash这个大程序里的小程序叫做内部程序
和bash 互相独立的程序叫做外部程序
所以bash也有自己的环境变量
环境变量又分为自定义变量和环境变量,自定义变量只是适用于当前进程,环境变量是子进程可以继承的环境变量。
当我们运行一个程序时,这个程序实在bash中运行的。其实是bash用fork函数创建了一个子进程,子进程复制了bash的映像,然后在子进程的映像中调用了exec系列的函数,将我们写的程序的可执行文件(映像)替换为子进程从父进程那里复制来的映像。然后执行我们写的程序的可执行文件。
fork之后bash下建立了一个子进程,开始这个子进程和bash是共用一个PCB的,当子进程发生改变时,子进程复制了父进程的PCB并调用了exec系列函数,将你要运行的程序的进程的PCB替换掉你从父进程那里复制来的PCB。然后执行你的程序。
以上为个人总结!如有不妥请指教!