在写进程池之前,我们再来看看进程原语函数。(之前没有整理出来)
1.fork
fork会创建出一个子进程,子进程有自己的虚拟地址空间0-4G,父进程继续执行后面的代码,子进程完全复制(clone)了父进程一份(0-3G),那么如何标识子进程父进程的区别,PCB中的pid号不同。
fork函数十分简洁
没有参数, 返回用来存放pid
fork:
(1)调用一次,返回两次。因为是clone,所以整段代码在父子进程中都有,在父进程中调用返回子进程的PID,在子进程中调用则返回0。
读时共享,写时复制
(2)copy on write
写入时复制(Copy-on-write)是一个被使用在程序设计领域的最佳化策略。其基础的观念是,如果有多个呼叫者(callers)同时要求相同资源,他们会共同取得相同的指标指向相同的资源,直到某个呼叫者(caller)尝试修改资源时,系统才会真正复制一个副本(private copy)给该呼叫者,以避免被修改的资源被直接察觉到,这过程对其他的呼叫只都是通透的(transparently)。此作法主要的优点是如果呼叫者并没有修改该资源,就不会有副本(private copy)被建立。
两者的虚拟空间不同,但其对应的物理空间是同一个。即父子进程在逻辑上仍然是严格相互独立的两个进程,各自维护各自的参数,只是在物理上实现了读时共享,写时复制。
vfork
一般用于fork后马上调用exec函数,vfork早期使用的较多,目前用的不多了,被淘汰。
2.exec( )族
从硬盘中加载一个新的程序去覆盖当前的代码段、数据段、堆、栈。程序从新程序中退出。
一般fork出子进程后调exec加载新的程序去执行。(所以copy on write的优化是十分必要的,否则我们一fork就去exec,那我如果此时就复制了父进程浪费就太大了。)
我们的shell就是类似的原理,只是我们在shell去执行类似ls、cat,shell跑到了后台,执行结束后再回到前台。
exec族这么多函数功能类似,但参数略有区别。
函数族中exec
p表示去PATH的环境变量找可执行程序
l表示命令行列表
v表示命令列表自己先初始化一个argv[ ]
e表示env,即给出一个环境变量的数组
示例:
而事实上,这么多函数只是接口不同,只有execve是真正的系统调用。
而我们很少用环境变量参数。
应用示例:
warper.c
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main(int argc,char *argv[]){
int fd;
if(argc!=2){
fputs("usage:wrapper file",stderr);
exit(1);
}
fd=open(argv[1],O_RDONLY);
if(fd<0){
perror("open");
exit(1);
}
dup2(fd,STDIN_FILENO);//将文件输入到shell
close(fd);
execl("./upper","upper",NULL);//参数是占位符
perror("exec ./upper");
exit(1);
}
upper.c
#include<stdio.h>
#include<ctype.h>
int main(){
int ch;
while((ch=getchar())!=EOF){
putchar(toupper(ch));
}
return 0;
}
思考:
将我的一篇代码转到标准输入,因为upper只覆盖0-3G,内核空间中的PCB的文件描述符表不变且还是在同一进程下,upper执行将标准输入转成了大写后输出。其实是通过warper实现对upper的重定向。
参考:
APUE及itcast的Linux系统编程