C语言创建子进程

C语言创建子进程

程序运行的时候,可以创建与自己关联的子进程,创建了这个子进程之后,可以选择等待这个子进程执行完毕,也可以让子进程与自己并行执行,还可以终止自己转而执行子进程。这些操作都是通过一系列相似而又有细微区别的库函数来实现。下面对这些可以使用的库函数进行介绍。

system()

它的函数定义如下:

#include <stdlib.h>

int system(const char * string);

这个进程会在系统中另外启动一个shell,并在新的shell中执行参数string给定的命令。如果无法启动shell,这个函数会返回127错误码,其他错误返回-1,否则返回该命令的退出码。

在程序中调用这个函数之后,调用进程会等待新shell中命令执行完成,之后在继续执行。但是也可以在string参数的命令后面加上&让命令后台执行,这样就能实现调用进程与新shell进程并行运行。

下面是一个例子:

#include <stdlib.h>
#include <stdio.h>

int main(void){
	char * command = "ps -al";	//新shell中将要运行的命令

	printf("Now running a new process.\n");
	system(command);
	printf("Returned from the sub process.\n");

	return 0;
}

编译并执行上面的程序会得到下面的输出:

Now running a new process.
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1000  5691  5646  0  80   0 - 581762 poll_s pts/0   00:00:04 vim
0 S  1000  6424  6215  0  80   0 -   570 do_wai pts/1    00:00:00 a
0 S  1000  6425  6424  0  80   0 -   598 do_wai pts/1    00:00:00 sh
0 R  1000  6426  6425  0  80   0 -  2897 -      pts/1    00:00:00 ps
Returned from the sub process.

可以看到程序运行到一半时,创建了一个子进程ps,等到子进程执行完毕,才回到自己的程序继续执行。

如果在执行的命令ps -al后面加上&,就会让子程序后台运行,从而调用程序与子程序就能并行运行。

exec系列函数

上面的system()需要启动一个新shell,并在新的shell中执行子程序,这样做的执行效率不高,并且因为不同的环境shell版本不一样,所以也会造成兼容性的问题。因此我们在需要创建子进程的时候使用exec系列函数,而不是system()

exec系列函数的作用都是结束本程序的运行,转而执行另外一个程序。新创建的子程序集成了原来进程的资源,有着同样的pid

新的程序默认状态下会继承已打开的文件,但是如果文件使用fcntl()打开并且设置了FD_CLOEXEC,则执行新进程的时候会关闭文件。

下面是所有exec系列函数的定义:

int execl(const char *pathname, const char *arg, ...)
int execv(const char *pathname, char *const argv[])
int execle(const char *pathname, const char *arg, ..., char *const envp[])
int execve(const char *pathname, char *const argv[], char *const envp[])
int execlp(const char *filename, const char *arg, ...)
int execvp(const char *filename, char *const argv[])

其中文件名的不同表示了传递参数的方式,查找程序的方式以及是否可以设置环境变量不同。对于名字的前四个字母exec都是统一的函数名,后面跟上l表示按照参数列表的方式接受多个函数参数;v表示利用字符串数组来接受多个参数;p表示系统将通过搜索PATH环境变量来查找程序;e表示可以传入环境变量。

下面是一个使用exec系列函数从当前进程启动ps程序的例子,在这个例子中使用了上面的所有方法:

#include <unistd.h>
#include <stdio.h>

int main(void){
	char * const ps_argv[] = {"ps", "-al", 0};	//就算使用数组的形式传入参数,也要用0表示参数的结尾
	char * const ps_envp[] = {"PATH=/bin:/usr/bin", "TERM=console", 0};

	//下面的函数只有第一个可以执行,剩余的只是作为演示
	execl("/bin/ps", "blahhh", "-al", 0);

	//剩下的函数不会被执行
	execlp("ps", "ps", "-af", 0);
	execle("/bin/ps", "ps", "-af", 0, ps_envp);
	execv("/bin/ps", ps_argv);
	execvp("ps", ps_argv);
	execve("/bin/ps", ps_argv, ps_envp);
}

关于这个程序,有几个需要注意的地方:

  • 程序执行了第一个execl()函数就会停止,转而执行参数中指定程序,除非指定程序执行失败,才会造成函数返回,从而继续往下执行。
    在往新程序中传入参数的时候,注意第一个参数是不读取的。在execl("/bin/ps", "blahh", "-al", 0);中,第一个参数表示要执行的程序,而第二个参数是传入程序的第一个参数,但这个参数不被读取,所以这里随便输入一也没关系,程序会从第三个参数开始读入程序的参数。这个规则对于用数组读取参数的函数也使用,在ps_argv中第一个参数也不是-al
  • 不管用函数参数还是数组传入参数,最后一个参数都要是0以表示结束。

fork()函数

使用fork()函数可以复制当前的进程。新创建的进程与原来的进程几乎完全一样,但是两个进程都有自己的数据,环境和文件描述符。并且新创建的进程是当前进程的子进程,基于这个特性,可以使用fork()先创建一个与当前进程一模一样的子进程,然后使用exec()函数使子进程转而执行另一个程序,就可以使另外一个子进程成为当前进程的子进程。

如果子进程创建失败,fork()函数会返回-1,并且将错误码保存在errno中,如果调用成功则返回子进程的PID。因为子进程跟父进程执行的代码相同,所以在子进程中也会有一个fork()函数,但是子进程中的fork()函数不会创建另外一个子进程,因为如果这样就会无限循环下去,取而代之子进程的fork()函数不会创建任何子进程,同时返回0,可以用这一点来判断当前进程是一个父进程还是一个子进程。下面是一个例子:

#include <stdio.h>
#include <unistd.h>

int main(void){
	int res = fork();
	switch (res){
		case -1:
			printf("Failed to creat a new sub process.\n"); break;
		case 0:
			printf("This is a sub proccess.\n"); break;
		default:
			printf("This is a parent process.\n");
	}

	return 0;
}

等待一个进程

当使用fork()函数创建一个子进程之后,子进程与父进程是并行执行的,如果想将父进程挂起知道子进程执行结束,可以使用wait()函数。这个函数会让父进程等待到子进程执行完毕之后在继续执行。当子进程结束之后,这个函数会返回子进程的PID,并且会将子进程返回的状态码保存到指定位置stat_loc,指定的位置只需要一个整型变量即可。

下面是该函数的定义:

#include <sys/wait.h>
#include <sys/types.h>

pid_t wait(int *stat_loc);

函数返回的状态信息被保存在一个整型数里,这个数据不是人能看得懂的,如果要查看子进程结束时的状态,可以使用sys/wait.h中定义的宏来实现,常用的宏有:

说明
WIFEXITED(stat_val)如果子进程正常结束,它就取一个非零值
WEXITSTATUS(stat_val)如果子进程正常结束,返回子进程的退出码
WIFSIGNALED(stat_val)如果子进程因为一个未捕获的信号终止,返回非零值
WTERMSIG(stat_val)如果子进程因为一个未捕获的信号终止,返回信号代码
WIFSTOPPED(stat_val)如果子进程意外终止,返回一个非零值
WSTOPSIG(stat_val)如果子进程意外终止,返回一个信号代码

下面是一个例子:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main(void){
	int stat;
	sleep(1);
	pid_t pid = fork();
	pid_t wt = wait(&stat);
	if(pid){
		printf("Contents in stat is: %d\n", stat);
		printf("WIFEXITED = %d\n", WIFEXITED(stat));
	}

	return 0;
}

这个函数有另外一个加强版本,它会让程序等待另外任意一个程序执行完毕。它的定义为:

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int * stat_loc, int options);

这个函数的返回值跟wait()函数一样,但是参数却不一样,pid表示要等待的进程的pidstat_loc表示要保存进程返回状态的地方,options是用于控制该函数行为的选项,其中一个常用的选项是WNOHANG,它的作用是防止调用程序在等待另一个进程的时候被挂起。

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值