1.fork函数
(1)pid_t fork(void); 创建一个子进程,子进程所有的数据,代码都是从父进程开呗过来的。失败返回-1,成功则返回:父进程返回子进程的ID,子进程返回0。pid_t类型表示进程ID,但是为了表示-1,它是有符号整型(0不是有效的进程ID,init的进程ID是最小的,1)。系统中能创建的进程的数量有系统资源决定,如果进程退出之后没有回收进程资源,那么这部分资源就会被一直占用着。
进程ID相关的函数:
getpid()函数;//获取当前进程ID pid_t getpid(void);
//获取当前进程的父进程的ID pid_t getppid(void);
getgid()函数;//获取当前进程使用用户组ID gid_t getgid(void);
//获取当前进程有效用户组ID gid_t getegid(void);
getuid()函数;//获取当前进程实际用户ID uid_t getuid(void);
//获取当前进程有效用户ID uid_t geteuid(void);
进程的创建:
#include <sdtio.h> #include <unistd.h> int main() { pid_t id = fork(); if(id == -1){ perror("create fail:"); return -1; } if(id == 0){ printf("本进程是子进程:%d-----父进程ID:%d",getpid(),getppid()); } if(id>0){ printf("本进程是父进程:%d-----子进程ID:%d",getpid(),id); } while(1); return 0; }
区分一个函数是系统函数还是库函数的依据:
1.能否访问内核数据结构
2.能否访问外部硬件资源
二者有任意一个就是系统函数,都没有则是库函数。
常用的查看当前系统进程的命令:ps aux
查看系统能创建多少进程:
int main() { int number = 0; while(1) { pid_t pid = fork(); if(pid<0) { perror("创建失败"); sleep(1); } if(pid == 0){ exit(-1);//子进程退出 } if(pid>0){ //父进程回收子进程资源 int status; wait(&status);//阻塞直到子进程退出 printf("number = %d ",number++); } } }
僵尸进程:子进程退出之后,父进程没有对子进程的资源进行回收,这个时候子进程就是僵尸进程,一定要避免僵尸进程的产生,僵尸进程的数量是有限的,有系统的最大资源决定。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid = fork(); if(pid ==0)exit(-1); if(pid>0) { while(1); } }
孤儿进程:子进程还在运行,但是父进程退出了,那么这时候子进程就是孤儿进程,孤儿进程对系统不会有影响,因为孤儿进程会被自动回收。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid = fork(); if(pid == 0){ while(1); } if(pid>0) { //父进程退出 sleep(10); exit(-1); } return 0; }
进程退出:exit(-1);
进程退出的方式:1.进程运行完之后自动退出
2.通过exit(-1)退出 进程关闭文件描述符,清空缓冲区
3._exit()退出 只是关闭进程,不会处理文件描述符,也不会清空缓冲区。
#include <stdio.h> #include <stdlib.h> int main(void) { printf("hello world"); _exit(0); return 0; }
(2)父子进程在fork()之后,有哪些异同?
查看父子进程共享的数据:
#include <sdio.h> #include <unistd.h> int main() { pid_t id = fork(); if(id == -1){ perror("create fail:"); return -1; } if(id == 0){ printf("本进程是子进程:%d-----父进程ID:%d",getpid(),getppid()); } if(id>0){ printf("本进程是父进程:%d-----子进程ID:%d",getpid(),id); } while(1); return 0; } #include <stdio.h> #include <unistd.h> int gdata = 123;//全局数据---数据段 int main() { int mydata = 123;//局部数据--栈空间 pid_t pid = fork(); if(pid == 0) { while(1) { //子进程 printf("子进程---gdata=%d mydata=%d",gdata,mydata); sleep(1); } } if(pid>0) { while(1) { printf("父进程---gdata=%d mydata=%d",gdata,mydata); sleep(1); mydata++; gdata++; } } return 0; }
相同的部分:全局变量、.data、.text、堆、栈、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式.....
不同的部分:进程ID、fork的返回值、两者的父进程ID、程序运行时间、闹钟(定时器)、未决信号集。
父子进程共享的区域:
1.文件描述符
2.mmap简历的映射区,fork之后,父子进程的先后执行顺序是不确定的,取决于内核所使用的的调度算法。
2.vfork()函数
fork()和vfork()都是用来创建子进程的,vfork()-----父进程要等子进程退出后才运行(写时复制),而fork()的父子进程的执行顺序是由系统调度决定的。
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> int main() { pid_t pid = vfork(); if(pid == 0){ printf("子进程 "); while(1); } if(pid>0){ printf("父进程 "); } }
3.exec函数族
(1)fork()创建子进程后执行的是和父进程相同的程序(但有可能是执行不同的代码分支),子进程往往要调动一种exec函数来执行另一个程序。当进程调用一种exec函数时,该进程用户空间代码和数据完全被程序替换,从新程序的启动例程开始执行。调用exec函数并不创建新进程,所以调用exec函数前后的进程ID并没有改变。
将当前进程的.text、.data替换为所要加载程序的.text、.data,然后让进程从新的.text的第一条指令开始执行,但是进程ID并没有改变,只是换核不换壳。
exec函数如下:
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 execve(const char *path,char *const argv[],char *const envp[]);
1.execlp函数
int execlp(const char *file,const char *arg....);//加载一个进程,借助PATH环境变量。
成功:无返回;失败:-1;
参数1:要加载的程序的名字,该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索之后没有参数1则返回失败。该函数通常用来调用系统程序,例如:ls、date、cp、cat等命令。
2.execl函数
int execl(const char *path,const char *arg,....);//加载一个进程,通过:路径+程序名来加载
成功:无返回;失败:-1;
参数1:要加载的程序的绝对路径。
3.execvp函数
int execvp(const char *file,char *const argv[]);//加载一个进程,使用自定义环境变量env
成功:无返回;失败:-1;
参数1:要加载的程序名,execvp和execlp原理上一样,只是参数不同。
4.exec函数族的一般规律
exec函数一旦调用成功就立刻执行新的程序,无返回值,错误则返回-1,所以我们通常直接在exec()函数调用后直接调用perror()和exit(),无需if判断。
l(list) | 命令行参数列表 |
p(path) | 搜索file时使用path变量 |
v(vector) | 使用命令行参数数组 |
e(environment) | 使用环境变量数组,不适用进程原有的环境变量,设置新加载程序运行的环境变量 |
事实上只有execve才是真正的系统调用,其他五个函数最终都是调用execve,这些函数之间的关系如下。
#include <stdio.h> #include <unistd.h> #include <sys/types.h> int main() { pid_t pid = fork(); if(pid=0){ char *const argv[]={"ls","-1","-a",NULL}; execv("/bin/ls",argv); printf("******** "); } if(pid>0){ printf("####### "); while(1); } return 0; }
PS:哪里写错了请指正,互相学习。