1.实验目的与要求
- 知道进程相关概念
- 获取进程信息
- 会进程的创建和终止
- 会进程的调用
2.实验平台
实验室安装的实验环境(Linux操作系统)和头歌(www.educoder.net)实验平台(课程实验)
3.实验内容
- 获取进程
- 进程的创建和终止
- 进程的调用
4.实验详细内容、步骤
任务描述
在 Linux
环境下,进程是一个十分重要的概念。每个进程都由一个唯一的标识符来表示,即进程ID
,通常称为pid
。本关将介绍如何获取进程的pid
。
本关任务:学会使用C
语言在Linux
系统中获取进程的pid
以及父进程的pid
。
相关知识
Linux
系统中存在一个特殊的进程,即空闲进程(idle process
),当没有其他进程在运行时,内核所运行的进程就是空闲进程,它的pid
为0
。在启动后,内核运行的第一个进程称为init
进程,它的pid
是1
。通常,Linux
系统中init
进程就是我们在资源管理器中看到的名为init
的程序。系统中其它的进程都是由init
来创建出来的。
创建新进程的那个进程被称为父进程,而新创建的进程被称为子进程。每个进程都是由其他进程创建的(除了init
进程),因此每个子进程都有一个父进程。
Linux
系统提供了两个系统调用函数来获取一个进程的pid
和其父进程的pid
,分别是getpid
和getppid
函数。在Linux
系统中可以使用man
命令来查询这些函数的使用方法。具体的查询命令为: man 2 函数名
获取进程自身pid
获取进程本身的进程ID
的系统调用函数是getpid
,具体的说明如下:
- 需要的头文件如下:
- i. #include <sys/types.h>
- ii. #include <unistd.h>
- 函数格式如下:
pid_t getpid(void);
- 函数返回值说明: 返回当前进程的
pid
值。
案例演示1
: 编写一个程序,打印自身的进程ID
。详细代码如下所示:
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- int main()
- {
- pid_t pid = getpid();
- printf("当前进程的ID为:%d\n", pid);
- return 0;
- }
将以上代码保存为getpid.c
文件,编译执行。可以看到每次运行都打印出不同的进程ID
,这是因为Linux
系统动态的给进程分配pid
。
获取父进程pid
获取父进程的进程ID
的系统调用函数是getppid
,具体的说明如下:
- 需要的头文件如下:
- i. #include <sys/types.h>
- ii. #include <unistd.h>
- 函数格式如下:
pid_t getppid(void);
- 函数返回值说明: 返回当前进程的父进程的
pid
值。
案例演示1
: 编写一个程序,打印父进程ID
和自身进程ID
。详细代码如下所示:
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- int main()
- {
- pid_t pid = getpid();
- printf("当前进程的ID为:%d\n", pid);
- pid_t ppid = getppid();
- printf("当前进程的父进程ID为:%d\n", ppid);
- return 0;
- }
将以上代码保存为getppid.c
文件,编译执行。可以看到每次运行都打印出相同的父进程ID
,这是因为我们在同一个终端中运行3
次程序,所以被运行的程序父进程为终端进程,因为父进程一直都一样。
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
- 补全
getProcInfo
函数,用于获取当前进程ID
和其父进程ID
(提示:将结果存放在procIDInfo结构体中)。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。
任务描述
在上一关我们学习如何获取进程的pid
信息,本关我们将介绍如何编程创建一个新的进程。
本关任务:学会使用C
语言在Linux
系统中使用fork
系统调用创建一个新的进程。
相关知识
在Linux
系统中创建进程有很多函数可以使用,其中包括了系统调用也包括库函数。本关将介绍一个最常见的系统调用函数来创建进程,这就是使用fork
函数来创建一个新进程。
当用户调用fork
函数时,系统将会创建一个与当前进程相同的新进程。通常将原始进程称为父进程,而把新生成的进程称为子进程。子进程是父进程的一个拷贝,子进程获得同父进程相同的数据,但是同父进程使用不同的数据段和堆栈段。
在早期的系统中,创建进程比较简单。当调用fork
时,内核会把所有的内部数据结构复制一份,复制进程的页表项,然后把父进程的地址空间中的内容也复制到子进程的地址空间中。但是从内核角度来说,这种复制方式是非常耗时的。
因此,在现代的系统中采取了更多的优化。现代的Linux
系统采用了写时复制技术(Copy on Write
),而不是一创建子进程就将所有的数据都复制一份。
Copy on Write
(COW
)的主要思路是:如果子进程/父进程只是读取数据,而不是对数据进行修改,那么复制所有的数据是不必要的。因此,子进程/父进程只要保存一个指向该数据的指针就可以了。当子进程/父进程要去修改数据时,那么再复制该部分数据即可。这样也不会影响到子父进程的执行。因此,在执行fork
时,子进程首先只复制一个页表项,当子进程/父进程有写操作时,才会对所有的数据块进行复制操作。
[COW
思路]
在Linux
系统中可以使用man
命令来查询该函数的使用方法。具体的查询命令为: man 2 函数名
使用fork函数创建进程
fork
函数的具体的说明如下:
- 需要的头文件如下:
- i. #include <unistd.h>
- 函数格式如下:
pid_t fork(void);
- 函数返回值说明: 调用成功,
fork
函数两个值,分别是0
和子进程ID
号。当调用失败时,返回-1
,并设置错误编号errno
。
注意:fork
函数调用将执行两次返回,它将从父进程和子进程中分别返回。从父进程返回时的返回值为子进程的 PID
,,而从子进程返回时的返回值为0
,并且返回都将执行fork
之后的语句。
案例演示1: 编写一个程序,使用fork
函数创建一个新进程,并在子进程中打印出其进程ID
和父进程ID
,在父进程中返回进程ID
。详细代码如下所示:
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <string.h>
- #include <errno.h>
- int main()
- {
- pid_t pid;
- pid = fork();
- if(pid == -1)
- {
- //创建进程失败
- printf("创建进程失败(%s)!\n", strerror(errno));
- return -1;
- }
- else if(pid == 0)
- {
- //子进程
- printf("当前进程为子进程:pid(%d),ppid(%d)\n", getpid(), getppid());
- }
- else
- {
- //父进程
- printf("当前进程为父进程:pid(%d),ppid(%d)\n", getpid(), getppid());
- }
- //子进程和父进程分别会执行的内容
- return 0;
- }
将以上代码保存为forkProcess.c
文件,编译执行。可以看到每次执行forkProcess
时,子进程和父进程都不是固定的执行顺序,因此由fork
函数创建的子进程执行顺序是由操作系统调度器来选择执行的。因此,子进程和父进行在执行的时候顺序不固定。
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
- 补全
createProcess
函数,使用fork
函数创建进程,并在子进程中输出"Children"
字符串,在父进程中输出"Parent"
字符串。(注意:不要在createProcess
函数中使用exit
函数或者return
来退出程序)。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。