1、fork函数—创建进程的函数
fork函数:
头文件:#include <unistd.h>
函数原型:pid_t fork(void)
fork函数的返回值: fork函数的返回值有三种,(1)创建失败,则fork返回一个负值;(2)若创建成功,则在父进程中,fork返回新创建的子进程的pid号;同时,在子进程中,fork函数返回0值。
2、fork函数例程
要求:让进程a(父进程)去创建新的进程b(子进程)
#include <unistd.h>
#include <stdio.h>
int main(void)
{
//定义变量,用该变量来接收fork的返回值
pid_t pid;
pid = fork();
//判断fork函数返回值的三种情况
//第一种情况:返回值小于0,则创建进程失败
if(pid <0){
printf("fork is error\n");
return -1;
}
//创建进程成功的话,返回的是子进程的pid号
if(pid >0){
printf("This is parent,parent pid is %d\n",getpid());
}
//创建成功的话,子进程会返回0值
if(pid == 0){
printf("This is child,child pid is %d, parent pid is %d\n",getpid(),getppid());
}
}
在该例程中,有一点需要说明: 想要获取进程的pid号,在C语言中使用的是getpid()函数以及getppid()函数
在ubuntu界面的编译以及运行结果如下图所示: 由运行结果可以看出,在ubuntu界面编译运行之后,会将父进程以及子进程的if语句满足条件后的结果均执行出来。 即:既打印了
This is parent,parent pid is 2479
又打印了
This is child,child pid is 2480, parent pid is 2479
为什么呢? 我们可以将父进程执行fork函数之后创建的子进程看做是父进程的拷贝,这样的话,父进程以及子进程在地址空间里面的内容都一样,代码也一样。执行父进程的过程中,会自动选择执行下面的代码:
if(pid >0){
printf("This is parent,parent pid is %d\n",getpid());
}
而子进程会选择执行pid==0的情况时候的代码,所以在ubuntu界面的执行结果是打印了两句。 注意:子进程的数据空间、堆栈空间都会从父进程中拷贝,但是它们的pid号是不一样的,所以可以通过pid号来区分父子进程;也可以通过fork函数的返回值来区分父子进程。 思考: (1)子进程创建成功以后,它的代码的执行位置在哪里呢? 父进程一定是从程序的开头开始执行,一直到程序结束。而子进程是从fork函数开始执行的。 (2)父子进程的执行顺序是什么嘞? 观察fork函数例程的执行结果,可以看到是父进程先执行的,那么一定是每次都先执行父进程么?不是这样的,父子进程的执行顺序是不一定的,因为父子进程的执行也需要抢cpu资源,哪个先抢到,哪个就先执行。
3、exec函数族
在linux中并没有exec函数,而是有6个以exec开头的函数族,下面列举exec函数族的6个函数成员的函数原型
int execl(const char *path, const char *arg, .../* (char *) NULL */);(最重要的一个)
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, 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[]);
exec函数族在使用时,最重要的思想就是“换核不换壳”。怎么解释呢?就相当于是用一个纸箱子盛放了苹果,执行了exec函数族之后,纸箱子没发生任何变化,只是纸箱子的东西发生了变化,纸箱子里可能变成梨啦。 exec函数族的使用场景: (1)当进程认为自己不能再为系统和用户做出任何的贡献时,就可以调用任何的exec函数族让自己重生; (2)如果一个进程想要执行另一个程序时,那么它就可以调用fork函数创建一个新的进程,然后调用任何一个exec函数使得子进程重生。
4、execl函数示例
c代码如下:(在fork函数c代码的基础上进行的修改,看子进程与父进程打印出来的i值分别是多少)
#include <unistd.h>
#include <stdio.h>
int main(void)
{
//定义变量,用该变量来接收fork的返回值
pid_t pid;
int i =0;
pid = fork();
//判断fork函数返回值的三种情况
//第一种情况:返回值小于0,则创建进程失败
if(pid <0){
printf("fork is error\n");
return -1;
}
//创建进程成功的话,返回的是子进程的pid号
if(pid >0){
printf("This is parent,parent pid is %d\n",getpid());
}
//创建成功的话,子进程会返回0值
if(pid == 0){
printf("This is child,child pid is %d, parent pid is %d\n",getpid(),getppid());
}
i++;
printf("i is %d\n",i);
return 0;
}
在ubuntu界面编译运行的结果如下: 父进程是从头开始运行的,运行到后面会执行一次i++的操作,故i=1;子进程是从fork函数开始执行的,运行到输出结果之后也会进行一下i++的操作,故i=1。父进程与子进程的i是相互独立的,他们不是共享的
下面将子进程进行拦截修改,将打印子进程与父进程的pid号的代码修改为hello.c的代码,查看执行结果
c代码如下:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
//定义变量,用该变量来接收fork的返回值
pid_t pid;
int i =0;
pid = fork();
//判断fork函数返回值的三种情况
//第一种情况:返回值小于0,则创建进程失败
if(pid <0){
printf("fork is error\n");
return -1;
}
//创建进程成功的话,返回的是子进程的pid号
if(pid >0){
printf("This is parent,parent pid is %d\n",getpid());
}
//创建成功的话,子进程会返回0值
if(pid == 0){
printf("This is child,child pid is %d, parent pid is %d\n",getpid(),getppid());
execl("/home/samba/jincheng/hello","hello",NULL);
exit(1);
}
i++;
printf("i is %d\n",i);
return 0;
}
其中,“exit()”函数如果执行成功,则返回值为0,失败则返回1. 在ubuntu界面进行编译运行,结果如下:
root@ubuntu:/home/samba/jincheng# ./execl2
This is parent,parent pid is 2788
i is 1
This is child,child pid is 2789, parent pid is 2788
root@ubuntu:/home/samba/jincheng# hello world
5、execl函数调用shell命令
在上一个例程中实现了子进程调用hello文件并执行,那么是否可以使用execl函数来调用shell命令呢? 答案是是的,因为shell命令本身也是可执行文件。 举个例子,此时要调用ls这个命令,ls这个命令它的可执行文件在根目录下,在ubuntu界面使用ls /bin/ls即可看到 将上述子进程的
execl("/home/samba/jincheng/hello","hello",NULL);
改为:
execl("/bin/ls","ls","-al",NULL);
与在ubuntu界面直接使用shell命令的执行结果是一样的。
|