进程#第三章 进程的创建

本文详细介绍了Linux下创建子进程的四种方法:system()通过/bin/sh -c执行命令;fork()创建新进程,复制父进程的PCB结构体;exec函数族用新程序覆盖现有进程空间;popen()使用管道连接新旧进程,实现进程间通信。每个函数的工作原理、使用示例和特点进行了深入解析。

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;
}

输出结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值