启动新进程
- system 函数(使用 shell 启动新进程)
- exec 函数(替换进程映像,并未创建新的进程)
- fork 函数(复制进程映像,创建新进程,父子关系:原进程与新进程)
1. system 函数(使用 shell 启动新进程)
- 在一个程序的内部启动另一个程序,从而创建一个新进程。
- system 函数确实创建了一个新的进程,但是新进程和原进程之间没有父子关系!仅仅是用一个 shell 来启动想要执行的程序。
- 缺点:一般来说,使用 system 函数远非启动其他进程的理想手段,因为它 必须用一个 shell 来启动需要的程序。由于在启动程序之前需要先启动一个 shell,而且对 shell 的安装情况及使用环境的依赖也很大,所以使用 system 函数的效率不高。
- system 函数
#include <stdlib.h>
// 运行以字符串参数的形式传递给它的命令并等待该命令的完成。
// 命令的执行情况如同在 shell 中执行如下的命令
// $ sh -c string
int system(const char *string);
- 在 test1 中启动 test2,test1 代码如下:
/* test1.c */
#include <stdlib.h>
#include <stdio.h>
void main(){
int res;
printf("Running test2 with system\n");
// 启动 test2 并等待 test2 执行结束
res = system("./test2");
printf("Done. res=%d\n", res);
}
test2 代码如下:
/* test2.c,睡眠 3 秒,模拟耗时操作 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
printf("test2 running\n");
sleep(3);
printf("test2 done.\n");
exit(0);
}
运行 test1,执行结果如下(可以看到 test1 等待 test2 执行返回后,才继续执行):
ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test1 test1.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test2 test2.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test1
Running test2 with system // test1 运行
test2 running // test2 开始执行
test2 done. // test2 执行终止
Done. res=0 // test1 运行终止
查看进程信息,查看‘COMMAND’列,可以发现 test2 是通过 命令 ‘sh -c ./test2’ 启动。这里有3个进程,它们依次启动,pid 数值依次递增:
- (pid:9204)test1 进程
- (pid:9205)用于启动 test2 的 shell 进程(新进程)
- (pid:9206)test2 进程(新进程)
- 其中 test2 是我们想要启动的进程,而 shell 进程是用来启动 test2 的
ubuntu@cuname:~$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 9204 0.0 0.0 4352 636 pts/18 S+ 15:58 0:00 ./test1
ubuntu 9205 0.0 0.0 4504 752 pts/18 S+ 15:58 0:00 sh -c ./test2
ubuntu 9206 0.0 0.0 4352 624 pts/18 S+ 15:58 0:00 ./test2
当然也可以让 shell 在后台执行 test2 程序,使得 system 函数在 shell 命令结束后立即返回,将 test1 程序中
res = system("./test2");
修改为
res = system("./test2 &");
再次运行 test1,执行结果如下(可以看到 test1 先执行完毕,随后 test2 才执行结束):
ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test1 test1.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test1
Running test2 with system
Done. res=0 // test1 执行结束
test2 running
ubuntu@cuname:~/dev/beginning-linux-programming/test$ test2 done. // test2 执行结束
2. exec 函数(替换进程映像:原进程执行新程序,并未创建新进程)
- exec 函数较多,这里使用 execlp 函数
#include <unistd.h>
// 其参数个数可变,且以一个空指针结束
int execlp(const char *file, const char *arg0, ..., (char *)0);
- 在 test1 中执行 test2,test1 代码如下:
/* test1.c */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void main(){
int res;
printf("Running test2 with exec\n");
execlp("./test2", "aa", "bb", "cc", (char *)0);
printf("Done. res=%d\n", res); //
}
test2 代码如下:
/* test2.c,首先打印 main 函数接收到的参数 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
printf("test2 running\n");
// 打印参数
if(argc > 1){
for(int i = 0;i < argc;i++){
printf("arg: %s\n", argv[i]);
}
}
// 模拟耗时操作
sleep(5);
printf("test2 done.\n");
exit(0);
}
运行 test1,执行结果如下(可以看到 test1 的最后一个 printf 语句并没有被执行,而 test2 正确执行完毕):
ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test1 test1.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test2 test2.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test1
Running test2 with exec // test1 开始运行
test2 running // test2 的代码开始被执行
arg: aa
arg: bb
arg: cc
test2 done. // test2 的代码执行结束,进程终止
test1 执行期间,查看进程信息(其中并没有 test1 进程,而只有 test2 进程如下):
ubuntu@cuname:~$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 9389 0.0 0.0 4352 636 pts/18 S+ 16:40 0:00 aa bb cc
原因:在 test1 中调用 exec 函数后,运行中的程序开始执行 exec 调用中指定的新的可执行文件的代码(即:test2),程序并没有再返回到 test1 程序中。新的进程的pid / ppid 和 nice 值与原先的完全一样,只是执行代码做了替换。
3. fork 函数(创建新进程,父子关系:原进程与新进程)
- 通过调用 fork 函数,创建一个新进程。这个系统调用复制当前进程,在进程表中创建一个新的表项,新表项中的许多属性与当前进程是相同的。新进程几乎与原进程一模一样,执行的代码也完全相同,但新进程有自己的数据空间 / 环境和文件描述符。可以先使用 fork 函数创建新进程,然后在新进程中使用 exec 函数替换执行代码。
- 父进程中,fork 函数调用返回的是 新的子进程的 PID。
- 子进程中,fork 函数调用返回的是 0。
- 程序在调用 fork 时被分为两个独立的进程。程序 通过 fork 调用的返回值确定父/子进程。
- fork 函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
一个典型的使用 fork 的代码片段如下:
pid_t new_pid;
new_pid = fork();
switch(new_pid){
case -1:
// error
break;
case 0;
// 子进程
break;
default:
// 父进程
break;
}
- test1 中 联合使用 fork 和 exec 函数,在新进程中执行 test2 代码,test1 代码如下:
/* test1.c */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
void main(){
pid_t pid;
printf("parent process: Running test2 with fork/exec\n");
pid = fork();
switch (pid){
case -1:
// error
printf("error: fork failed.\n");
break;
case 0:
// 子进程
// 将子进程映像替换为 test2 程序
execlp("./test2", "aa", "bb", "cc", (char *)0);
// 子进程终止,下面的 'break;' 语句不会被子进程执行,
// 因为子进程的执行代码已经被 test2 程序代码所替换,
// 且 test2 执行完毕后,不会返回到原程序中。
break;
default:
// 父进程
sleep(5);
break;
}
printf("parent process over.\n");
}
test2 程序代码如下:
/* test2.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
printf("test2 running\n");
if(argc > 1){
for(int i = 0;i < argc;i++){
printf("arg: %s\n", argv[i]);
}
}
sleep(5);
printf("test2 done.\n");
exit(0);
}
执行 test1,运行结果如下:
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test1
parent process: Running test2 with fork/exec
test2 running // 子进程启动并替换映像为 test2 程序
arg: aa
arg: bb
arg: cc
parent process over. // 父进程终止
test2 done. // 子进程终止
test1 执行期间,进程信息如下(可以看到 父子进程分别为 test1 和 test2,不同于 system 函数,没有使用 shell 进程创建新进程):
ubuntu@cuname:~$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 9617 0.0 0.0 4352 784 pts/18 S+ 17:07 0:00 ./test1
ubuntu 9618 0.0 0.0 4352 624 pts/18 S+ 17:07 0:00 aa bb cc
4. 多进程实验
- 下列程序输出结果?
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
void main(){
printf("1 running.\n");
fork();
fork();
printf("222\n");
sleep(6);
}
程序执行流程如下图:
输出结果为(printf 语句执行 4次):
ubuntu@cuname:~/dev/beginning-linux-programming/test$ gcc -o test3 test3.c
ubuntu@cuname:~/dev/beginning-linux-programming/test$ ./test3
1 running.
222
222
222
222