启动新进程
  1. system 函数(使用 shell 启动新进程)
  2. exec 函数(替换进程映像,并未创建新的进程)
  3. fork 函数(复制进程映像,创建新进程父子关系:原进程与新进程)

1. system 函数(使用 shell 启动新进程)
  • 在一个程序的内部启动另一个程序,从而创建一个新进程。
  • system 函数确实创建了一个新的进程,但是新进程和原进程之间没有父子关系!仅仅是用一个 shell 来启动想要执行的程序。
  • 缺点:一般来说,使用 system 函数远非启动其他进程的理想手段,因为它 必须用一个 shell 来启动需要的程序。由于在启动程序之前需要先启动一个 shell,而且对 shell 的安装情况及使用环境的依赖也很大,所以使用 system 函数的效率不高。
  1. system 函数
#include <stdlib.h>
// 运行以字符串参数的形式传递给它的命令并等待该命令的完成。
// 命令的执行情况如同在 shell 中执行如下的命令
// $ sh -c string
int system(const char *string);
  1. 在 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 函数(替换进程映像:原进程执行新程序,并未创建新进程)
  1. exec 函数较多,这里使用 execlp 函数
#include <unistd.h>
// 其参数个数可变,且以一个空指针结束
int execlp(const char *file, const char *arg0, ..., (char *)0);
  1. 在 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 调用的返回值确定父/子进程
  1. 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;
}
  1. 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. 多进程实验
  1. 下列程序输出结果?
#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);
}

程序执行流程如下图:

linux服务器中的启动了多个nginx进程_ubuntu


输出结果为(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