Linux创建进程
一. 实验目的及实验环境
1.实验目的
通过观察、分析实验现象,深入理解进程及进程在调度执行和内存空间等方面的特点,理解进程创建和用户缓冲区的使用,掌握在POSIX 规范中fork和kill系统调用的功能和使用。
2.实验环境
(1)硬件
-
- CPU:1核
- 内存:2G
- 显示器:Lenovo Display 1920*1080
- 硬盘空间:50GB
(2)软件
-
- 操作系统名称及版本:CentOS 7.6 64位
- 使用shiyanlou的Xfce终端
二. 实验内容
1、实验前准备工作
学习man命令的用法,通过它查看fork和kill系统调用的在线帮助,并阅读参考资料,学会fork与kill的用法,复习C 语言的相关内容。
2、实验内容
根据下发的Linux进程管理实验PPT内容,完成子实验1和将子实验2代码补充完整。并考虑:
先猜想一下这个程序的运行结果。假如运行“./process 20”,输出会是什么样?然后按照注释里的要求把代码补充完整,运行程序。可以多运行一会儿,并在此期间启动、关闭一些其它进程,看process 的输出结果有什么特点,记录下这个结果。开另一个终端窗口,运行“ps aux|grep process”命令,看看process 究竟启动了多少个进程。回到程序执行窗口,按“数字键+回车”尝试杀掉一两个进程,再到另一个窗口看进程状况。按q 退出程序再看进程情况。
3、回答问题
编写、编译、链接、执行实验内容设计中的代码,并回答如下问题:
子实验1:fork面试题
1.你最初认为运行结果会怎么样?
我最初认为运行结果应该为输出6个‘-’。
2.实际的结果什么样?试对产生该现象的原因进行分析。
实际结果为8个‘-’。我们首先需要知道fork()系统调用的特性,fork()系统调用是Unix下以自身进程创建子进程的系统调用,一次调用,两次返回,如果返回是0,则是子进程,如果返回值>0,则是父进程(返回值是子进程的pid),这是众为周知的。还有一个很重要的东西是,在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,包括指令,变量值,程序调用栈,环境变量,缓冲区,等等。
因为printf(“-“);语句有buffer,所以,对于上述程序,printf(“-“);把“-”放到了缓存中,并没有真正的输出,在fork的时候,缓存被复制到了子进程空间,所以,就多了两个,就成了8个,而不是6个。
3.用户缓冲区如何使用?
用fflush刷缓存。
fflush(stdout);
4.把你的程序源代码附到实验报告后。
fork面试题原题解析链接https://mp.weixin.qq.com/s/PQmInO2jPnZu0Yv1oze-PA
子实验2:多进程创建及并发执行
1.你最初认为运行结果会怎么样?
最开始我觉得应该是num按顺序0~9进行。
2.实际的结果什么样?有什么特点?试对产生该现象的原因进行分析。
实际num是随机的,并且每隔SLEEP_INTERVAL秒刷新一次输出在命令行界面。
特点是每次都输出一定数目的进程,但是,发现每次输出的进程的次序不一样,随机输出。
每次调用fork(),都会生成一个父进程,一个子进程。把生成父进程返回的子进程pid保存到pid[i]中;把i直接赋给子进程的自编号proc_number,然后调用死循环函数do_something()进行输出。进程是循环创建的,所以进程自编号proc_number是随着i由小变大的,pid也是依次递增的。
3. proc_number 这个全局变量在各个子进程里的值相同吗?为什么?
不相同,因为进入子进程,子进程有独立的堆栈,只是复制了一份全局变量。
4.kill 命令在程序中使用了几次?每次的作用是什么?执行后的现象是什么?
两次。kill(pid[ch=’0’],SIGTERM);kill(0,SIGTERM);第一个是杀死进程号pid[ch-‘0’],执行后输出的结果中不会再有该进程号。第二次是杀死本组所有进程。即主进程以及它所创建的所有子进程,执行后程序退出结束。
5.使用kill 命令可以在进程的外部杀死进程。进程怎样能主动退出?这两种退出方式哪种更好一些?
进程在主函数中用return,或调用exit()都可以主动退出。
主动退出更好一点,因为使用kill()则是强制性的异常退出
6.写出fork()和kill()函数原型,并解释函数的功能和参数的含义?
fork()函数:pid_t fork(void);
返回值:fork仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
(1)在父进程中,fork返回新创建子进程的进程ID;
(2)在子进程中,fork返回0;
(3)如果出现错误,fork返回一个负值;
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
kill()函数:int kill(pid_t pid, int sig);
函数参数:①pid:指定进程的进程ID,注意用户的权限,比如普通用户不可以杀死1号进程(init)。
pid>0:发送信号给指定进程
pid=0:发送信号给与调用kill函数进程属于同一进程组的所有进程
pid<0:发送信号给pid绝对值对应的进程组
pid=-1:发送给进程有权限发送的系统中的所有进程
7.ps aux|grep process命令功能是什么?并解释结果的含义。
如果直接用ps命令,会显示所有进程的状态,通常结合grep命令查看某进
程的状态。
- 方案设计
- 测试数据及运行结果
五.总结
- 实验过程中遇到的问题及解决办法;
问题:运行程序得不到结果。
解决方法:一定要在修改完fork.c文件后在终端输入gcc fork.c -o fork重新编译该文件!完成后在终端输入./fork即可。
问题:遇到代码输入错误,导致结果输入不出来
解决办法:根据提示错误信息,返回代码界面,进一步修改测试
问题:遇到不理解的细节以及结果
解决办法:及时上网查询资料
- 对设计及调试过程的心得体会。
设计代码一定要足够完善,理清思路,要有逻辑地设计代码,清楚每一部分的功能以及作用,调试代码的过程中一定要细心,边改边测试。
六.附录:源代码(电子版)
子实验1:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int i;
for (i = 0; i < 2; ++i)
{
fork();
printf("-");
}
wait(NULL);
wait(NULL);
return 0;
}
子实验2:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <ctype.h>
#include <sys/types.h>
#include <stdlib.h>
#define MAX_CHILD_NUMBER 10
#define SLEEP_INTERVAL 2
int proc_number = 0;
void do_something();
int main(int argc, char* argv[])
{
int child_proc_number = MAX_CHILD_NUMBER;
int i, ch;
pid_t child_pid;
pid_t pid[10] = { 0 };
if (argc > 1)
{
child_proc_number = atoi(argv[1]);
child_proc_number = (child_proc_number > 10) ? 10 :
child_proc_number;
}
for (i = 0; i < child_proc_number; i++)
{
child_pid = fork();
if (child_pid == 0)
{
proc_number = i;
do_something();
}
else if (child_pid > 0)
{
pid[i] = child_pid;
}
}
while ((ch = getchar()) != 'q')
{
if (isdigit(ch))
{
kill(pid[ch - '0'], SIGTERM);
}
}
kill(0, SIGTERM);
return 0;
}
void do_something()
{
for (;;)
{
printf("-> %d -> %d\n", proc_number, getpid());
sleep(SLEEP_INTERVAL);
}
}