目录
fork()
介绍
功能:创建一个新的进程。
返回:子进程中为0,父进程中为子进程ID,出错为-1。
说明:
- 由fok创建的新进程被称为子进程( child process)。
- 父、子进程完全一样(代码、数据),子进程从fork内部开始执行,fork返回子进程的pid后,接着执行下一条语句该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是子进程的进程|D。
- 一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
继承
使用fork函数得到的子进程从父进程处继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
父、子进程之间的区别
- fork的返回值;
- 进程PID(当前进程的id)、PPID(当前进程的父进程的id)。
- 父进程设置的锁,子进程不继承;
- 子进程的未决告警被清除;
- 子进程的未决信号集设置为空集。
程序分析
下面程序中,当执行fork()时,就创建了一个子进程,然后子进程被加入到就绪队列中等待操作系统调度执行。当前进程在执行fork()之后会执行sleep(2)休眠2s,在这段时间子进程获得CPU并执行,所以子进程比父进程先打印。
以下是上面程序的执行结果:
a write to stdout before fork
pid=5485;ppid=5484,g1ob=7,var=89
pid=5484;ppid = 2375, glob = 6, var =88
fork与“Copy On Write”
fork()的功能是创建一个新的进程,很多时候这个新的进程是为了执行一个新的程序(而不是父进程中的程序),但是新的进程又要将父进程的进程空间中的内容copy一份,这就显得很多余而且浪费时间。所以,linux中进行了改进,采用改了“Copy On Write”技术,只有子进程要执行父进程中的程序时才将父进程进程空间的内容copy过来。
exec函数簇
介绍
函数簇介绍,在linux系统中输入"man exec"指令可以查看详细信息:
int execl(const char *path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
int execle(const char *path,const chr *arg,...,char * const envp[]);
int execv(const char *path,char * const argv[]);
int execvp(const char *file,char * const argv[]);
功能描述:exec函数簇用新进程替换当前进程,过程:调用进程——>exec(正常情况下不会返回)——>可执行的替换程序。
返回:exec函数只在发生错误时才返回,返回值为-1,返回后继续执行调动进程的下一行指令(exec函数的下一行代码)。
exec与fork
函数参数介绍
以execl()为例,execl()调用的参数均为字符型指针。
- 第一个参数path给出了被执行的程序所在的文件名,它必须是一个有效的路径名,文件本身也必须是一个真正的可执行程序。
- 第二个以及用省略号表示的其他参数一起组成了该程序执行时的参数表。由于参数的个数是任意的,所以必须用一个NULL指针来标记参数表的结尾。
// int execl(const char *path,const char *arg,...);
execl("/bin/ls","ls","-l",NULL);
上面的函数表示执行linux自带的可执行程序,程序所在的位置是/bin/ls,执行程序传入的参数是ls -l,这样我们就能将当前目录下的所有文件目录在终端上打印出来。
程序分析
程序1
int main() {
pid_t pid;
pid = fork();
if(pid < 0) {
perror("fork failed");
} else if(0 == pid) {
printf("子进程\n");
} else {
wait(10);
printf("父进程\n");
}
}
执行结果:
子进程
父进程
程序2
int main() {
pid_t pid;
pid = fork();
if(pid < 0) {
perror("fork failed");
} else if(0 == pid) {
execl("/bin/ls","ls","-l",NULL); // 子进程阻塞在这一行,即使/bin/ls执行成功
printf("execl执行错误\n");
} else {
wait(10);
printf("父进程\n");
}
}
执行结果:
【输出当前目录下的所有文件和目录名】
父进程
程序3
int main() {
pid_t pid;
pid = fork();
if(pid < 0) {
perror("fork failed");
} else if(0 == pid) {
execl("/bin/smdd","sm","dd",NULL); // 指定路径下的可执行程序文件不存在
printf("execl执行错误\n");
} else {
wait(10);
printf("父进程\n");
}
}
执行结果:
execl执行错误
父进程
总结
exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
与 一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表 面上的信息仍保持原样,颇有些神似"三十六计"中的"金蝉脱壳"。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个-1,从 原程序的调用点接着往下执行。
Linux下操作系统如何执行新的程序?
每当有进程认为自己不能为系统 和拥护做出任何贡献了,他就可以发挥最后一点余热,调用任何一个exec,让自己以新的面貌重生。
一个进程如何执行另外一个的程序(进程)?
如果一个进程想执行另一个程序, 它就可以fork出一个子进程,然后让子进程调用任何一个exec来覆盖自己,这样看起来就好像通过执行应用程序而产生了一个新进程一样。
事实上第二种情况被应用得如此普遍,以至于Linux专门为其作了优化,我们已经知道,fork会将调用进程的所有内容原封不动的拷贝到新产生的子进程中去,这些拷贝的动作很消耗时间,而如果fork完之后我们马上就调用exec,这些辛辛苦苦拷贝来的东西又会被立刻抹掉,这看起来非常不划算,于是人们设 计了一种"写时拷贝(copy-on-write)"技术,使得fork结束后并不立刻复制父进程的内容(而是存了一个),而是到了真正实用的时候才复制,这样如果下一条 语句是exec,它就不会白白作无用功了,也就提高了效率。