Unix环境高级编程之fork和vfork,exec函数族,wait和waitpid,exit和_exit

我之前一篇文章中写过fork与缓冲区的问题,写的主要是针对那个问题,今天就来讲一下常用的系统调用fork,exec,wait,exit,这几个系统调用关联性较强,一起介绍

fork

#include<unistd.h>
#include<sys/types.h>
pid_t fork(void);  //子进程返回0,父进程返回子进程pid

Linux下调用fork会创建一个子进程,这个子进程会接着执行父进程的代码,子进程独占一份内存空间,它会将父进程的所有可读写的段复制过来(比如数据段,栈,堆),而父进程只读的段比如代码段和一些只读数据段则会与子进程共享。所以在子进程修改变量不会影响父进程变量的值。
而由于fork后通常跟随着exec(在下面会介绍,exec会执行其参数指定的可执行程序,并且执行完后不会返回),既然这样,那么在fork后复制父进程的内存副本将会是没有用的操作。因此有了写时复制技术,当fork后并不直接复制一个副本,父子进程先共享一份内存,当有一个进程需要修改内存里的值时,内核只为修改区域那一块内存制作一个副本(通常是虚拟内存系统中的“页”,并且修改对应页表,将其虚拟地址映射到副本中)。
好了,fork就介绍到这里,现在来总结一下fork之后哪些内容父子进程共享,哪些内容父子进程不共享。
fork不共享的内容
1.数据段,栈,堆(也就是全局变量,动态分配(malloc)的空间,和局部变量)不共享
2.文件描述符表(fork会复制一份文件描述符表)
fork共享的内容
1.代码段
2.文件表项(包含文件状态,文件偏移量,v节点指针),文件表项属于系统层面的东西,会共享。
3.管道pipe

vofork

vfork参数和返回值和fork一样,但它们语义不同

#include<unistd.h>
#include<sys/types.h>
pid_t fork(void);  //子进程返回0,父进程返回子进程pid

vfork函数创建一个新进程,新进程目的是调用exec执行一个新程序,因此vfork被设计为会与父进程共享一份内存空间,当vfork创建的子进程调用exec后,就会为新程序分配新的内存空间。但如果vfork创建的子进程不执行exec或exit,而进行其他修改数据的操作,调用其他函数等都会带来未知的影响。
另外,vfork创建子进程后会保证子进程先运行,父进程在子进程调用exec或exit后才会恢复运行。

exec函数族

#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[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
//调用成功后执行新程序,不会返回。调用失败返回-1

先介绍各个函数怎么用
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表,如exec(“/bin/ls”,“ls”,“-a”,“/home”,NULL),最后一个参数必须是NULL,用来标识参数结束。
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。如execv(“/bin/ls”,argv);这里argv={“ls”,“-a”,“/home”,NULL};最后一个也要是NULL。
p:使用文件名,并从PATH环境进行寻找可执行文件,也就是若要执行的程序路径在环境变量PATH里面的话,第一个参数可以不用绝对路径
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
不带p和

tips:使用exec函数族要注意,第一个和第二个参数都是要执行的程序名,而执行程序的参数是从第三个参数开始的,这与main函数参数argv[0]里的值是程序的名字对应,通常对于忽略argv[0]的程序,exec第二个参数改成其他值也不影响,但它一定要有

wait和waitpid

#include<sys/wait.h>
pid_t wait(int* statloc);
pid_t waitpid(pid_t pid,int* statloc,int options);
//两个函数返回值,若成功返回进程ID,若出错返回0或-1

这两系统调用都是阻塞的,但waitpid可以通过options参数设置非阻塞

调用wait后,会阻塞到任意一个子进程终止。若调用时没有子进程,返回0。
int* statloc是一个传出参数,表示子进程终止状态,若不关心终止状态,则参数可以为NULL。

对于waitpid,
参数pid
1.pid==-1 waitpid等待任意子进程,此时与wait一样
2.pid>0 waitpid等待进程id为pid的子进程。
3.pid==0 waitpid等待组ID等于调用进程组id的任意子进程
4.pid<-1 waitpid等待组ID等于 | pid | 的子进程

options参数要么为0,要么为以下宏或的结果
WCONTINUED 若实现支持作业控制,那么pid指定的任一子进程停止后已经继续,但状态尚未报告,则返回其状态(看不懂什么意思。。。)
WNOHANG 若pid指定的子进程还未终止,则waitpid不阻塞,此时返回值为0.若pid指定子进程终止,则返回其进程ID。若调用时不存在参数pid对应的子进程,则返回-1(这与调用wait后没有子进程返回0不一样)
WUNTRACED 若某实现支持作业控制,而pid指定的任意子进程已处于停止状态,并且状态从停止后还未报告过,则返回其状态。(跟第一个宏一样看不懂。。。)

exit和_exit

#include<stdlib.h>
void exit(int status);//status为进程退出状态,定义0为正常退出,其他为异常退出

当调用exit(status)后,若其父进程调用wait或waitpid来获取退出状态,则status会被父进程捕获。调用exit会冲洗缓冲区,有的系统还会关闭所有标准IO流,而有的系统不会。若关闭标准IO流,由于文件表项父子进程是共享的,所以也会导致父进程标准IO流对象储存区被清0,父进程调用printf将不会产生任何输出。
而_exit与exit不同,_exit退出并不会冲洗子进程缓冲区。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值