您的位置:首页 > 其它

第三章 进程的创建

2016-01-26 00:18 267 查看
Linux下有四类创建子进程的函数:system(),fork(),exec*(),popen()

先执行父进程,后执行子进程

一、system()

原型:

#include <stdlib.h>
int system(const char *string);


system函数通过调用shell程序/bin/sh -c 来执行string所指定的命令,该函数在内部是通过调用fork()–>execve(“/bin/sh”…)函数来实现的。通过system创建子进程后,原进程和子进程各自运行,相互间关联较少。如果system调用成功,将返回0。

(原理:子进程占据了主进程的PCB区域运行,fork()–>execve()创建拷贝添内容)

示例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
system("sleep 10");
printf("I am main process\n");
while(1);
return 0;
}




二、fork()(拷贝PCB结构体)

原型:

#include <unistd.h>
pid_t fork(void);


在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。它和其他函数的区别在于:它执行一次返回两个值。其中父进程的返回值是子进程的进程号,而子进程的返回值为0.若出错则返回-1.因此可以通过返回值来判断是父进程还是子进程。

fork函数创建子进程的过程为:使用fork函数得到的子进程是父进程的一个复制品,它从父进程继承了进程的地址空间,包括进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级(0-99实时进程100-139非实时进程数字越大优先级越低)、进程组号、当前工作目录、根目录、资源限制、控制终端,而子进程所独有的只有它的进程号(子进程的进程号为父进程+1)、资源使用(物理资源使用,虚拟地址指向同一个物理地址,但操作权限不同,子进程写的时候os重新给子进程分配一块物理地址,是旧的物理地址复制,即写实复制,mmu机制)和计时器等。通过这种复制方式创建出子进程后,原有进程和子进程都从函数fork返回,各自继续往下运行,但是原进程的fork返回值与子进程的fork返回值不同,在原进程中,fork返回子进程的pid,而在子进程中,fork返回0,如果fork返回负值,表示创建子进程失败。(vfork函数)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid=fork();
if(pid==0)
{
printf("my pid is %d   ppid=%d\n",getpid(),getppid());
exit(0);
}
else
{
printf("my pid is %d   ppid=%d\n",getpid(),getppid());
while(1);
return 0;
}
}




说明:子进程的pid为父进程的pid+1,vfork把PCB结构体拷贝好一份后就把子进程放到对应的队列中,所以执行路径有两条。

三、exec函数族(exec*由一组函数组成)

原型:

int execl(const char *path, const char *arg, ...)


exec函数族的工作过程与fork完全不同,fork是在复制一份原进程,而exec函数是用exec的第一个参数指定的程序覆盖现有进程空间(也就是说执行exec族函数之后,它后面的所有代码不在执行)。

path是包括执行文件名的全路径名 。

arg是可执行文件的命令行参数,多个用,分割注意最后一个参数必须为NULL。

示例:

//add.c
#include <stdio.h>

int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("error args\n");
return 0;
}
printf("the sum is %d\n",atoi(argv[1])+atoi(argv[2]));
return 1;
}

//execl.c,整个代码段被add.c覆盖掉,返回值为add.c的返回值,为1,如下图
#include <unistd.h>
#include <stdio.h>

int main()
{
printf("I am start\n");
execl("./add","add","1","2",NULL);//NULL表示输入参数结束
printf("test\n");//此语句将不会被执行
return 0;
}




说明:echo $?语句功能:显示函数返回值



四、popen函数

原型:

#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);


popen函数类似于system函数,与system的不同之处在于它使用管道工作。command为可执行文件的全路径和执行参数;

type可选参数为”r”或”w”,如果为”w”,则popen返回的文件流做为新进程的标准输入流,即stdin,如果为”r”,则popen返回的文件流做为新进程的标准输出流。如果type是“r”,(即command命令执行的输出结果作为当前进程的输入结果)。被调用程序的输出就可以被调用程序使用,调用程序利用popen函数返回的FILE*文件流指针,就可以通过常用的stdio库函数(如fread)来读取被调用程序的输出;如果tpye是“w”,(即当前进程的输出结果作为command命令的输入结果)。调用程序就可以用fwrite向被调用程序发送数据,而被调用程序可以在自己的标准输入上读取这些数据。pclose等待新进程的结束,而不是杀新进程。(popen在创建进程的时候做了原进程的重定向,将两个进程用管道连接,若A、B分别为原进程和新进程,那么B的标准输出1重定向给A的标准输入0,A的标准输出1重定向给B的标准输入0,都通过管道)

示例:

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
int main()
{
FILE* fp;
fp=popen("ls -l","r");//打开标准流管道,并从管道中读取数据
if(NULL==fp)
{
perror("popen");
return -1;
}
char buf[512];
bzero(buf,sizeof(buf));
int ret=fread(buf,1,512,fp);
if(ret>0)
{
printf("%s\n",buf);
}
return 0;
}




//scanf.c
#include <stdio.h>

int main()
{
int i;
scanf("%d",&i);
printf("the i is %d\n",i);
return 0;
}

//popen_w.c
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
int main()
{
FILE* fp;
fp=popen("./scanf","w");
if(NULL==fp)
{
perror("popen");
return -1;
}
char buf[512];
bzero(buf,sizeof(buf));
strcpy(buf,"456");
int ret=fwrite(buf,1,strlen(buf),fp);
if(ret<0)
{
perror("fwrite");
return -1;
}
pclose(fp);
return 0;
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: