实验二进程的创建
一、实验目的
1.加深对进程概念的理解,明确进程和程序的区别。
2.掌握进程的创建方法
二、实验学时
2学时
三、实验内容
1.ps命令的使用(请将使用的命令和结果写在实验报告册中)
(1)显示所有进程,找出使用当前进程的进程ID号,查看其状态。
(2)列出目前所有面向用户的进程。(进程内可省略)
(3)显示所有自己所在用户的进程,并以长格式输出。
(4)用面向任务的格式显示个人用户中bash命令中的所有实例;并查看其父进程的详细信息。
2.at进程调度
(1)设置一个调度,要求在2019年10月1日0时,向所有用户发送国庆问候
[操作步骤]
(1)按组合键[Ctrl+Alt+F1]切换到第一个虚拟终端,以用户名aaa登录;按组合键[Ctrl+Alt+F2]切换到第二个虚拟终端,以超级用户身份登录字符界面,以下操作在该终端上完成;
(2)输入命令“at 00:00 10012019”,设置2019年10月1日0时执行的at调度;
(3)屏幕出现at调度的命令提示符“at >”,在提示符后输入“wall Happy New
Year!”,向用户发送问候语;
(4)在提示符后输入[Ctrl+D]结束at调度内容的输入,屏幕将根据设置显示作业号和将要运行的时间。(请给出截图或写在实验报告册中)
可调整系统时间为2019年12月31日23时59分(输入命令“date
123123592017”),稍等后观察at调度的执行效果,再切换到第一个虚拟终端观察at调度的执行效果;之后请切换到超级用户终端恢复系统时间为正确的时间。(请给at调度的执行结果截图或写在实验报告册中)
3.获取进程ID号
获取进程识别码函数getpid
表头文件: #include<unistd.h>
定义函数: pid_t getpid(void);
函数说明:
getpid()用来取得目前进程的进程识别码,许多程序利用取到的此值来建立临时文件,以避免临时文件相同带来的问题。
返回值: 目前进程的进程识别码
例:
#include<unistd.h>
main()
{
printf(“pid=%d\n”,getpid());
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vAzwDMnj-1573135215026)(media/2eaa82c16363225e74ee917e5071e683.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZWVrRPhK-1573135215027)(media/9201f2a739f36c7ef06858dfde1fb4b5.png)]
4.进程的创建
(1)编写一段程序,使用系统调用fork()创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”;子进程分别显示字符“b”和字符“c”。
参考程序
<程序2.1>
#include <stdio.h>
main( )
{
int p1,p2;
while((p1=fork( ))= = -1); /*创建子进程p1,失败的时候循环
fork创建子进程,一般子进程先运行*/
if (p1= =0) putchar(‘b’); /*子进程p1创建成功*/
else /*父进程返回,当子进程执行完成之后父进程往下执行,再一次生*/
{
while((p2=fork( ))= = -1); /*创建子进程p2,失败循环*/
if(p2= =0) putchar(‘c’); /*子进程p2创建成功*/
else putchar(‘a’); /父进程执行/
}
}
分析并运行该程序回答如下问题,将结果与分析写在实验报告中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ELetCxeW-1573135215028)(media/4b61a2866ed0b634f25fc716c98db3aa.png)]
问题1:该程序的运行结果为何?多次运行后结果是否相同,为什么?
答:运行结果如上图所示,多次运行的结果并不相同,fork()描述的是一个进程创建的过程,出现“abc、bac、acb”等不同的结果只是说明父进程和子进程在交替的占用资源。
问题2:修改该程序,使之在打印结果的同时标出是由哪个程序运行的出来的。(利用getpid()函数)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-txJa7AYs-1573135215028)(media/e0e43348cdfc1233488f5a0dd77e23d2.png)]
(2)进程的父进程和子进程
编写一段程序,使用系统调用fork()创建一个子进程。让子进程和父进程并发同时在每个进程中显示其子进程与父进程的PID号。
<程序2.2>
#include <stdlib.h>
#include <stdio.h>
#include<unistd.h>
int main(void)
{
pid_tpid;
printf(“before calling fork,calling process pid = %d\n”,getpid());
pid = fork();
if(pid == -1) /*创建失败*/
{printf(“fork fail!\n”);
exit(1);} /*表示异常退出.这个1是返回给操作系统的*/
if(pid == 0){
printf(“this is child process and child’s pid = %d,parent’spid
%d\n”,getpid(),getppid());
}
if(pid> 0){
//sleep(1);
printf(“this is parent process and pid =%d ,child’s pid = %d\n”,getpid(),pid);
}
return 0;
exit(0); /*表示正常退出*/
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xt29fzHd-1573135215029)(media/7a11812cee81996c089002c5cb8965f2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dhRBKzS8-1573135215029)(media/42bfb471b0dd21f8b3576d34b21255b9.png)]
分析并运行该程序回答如下问题,将结果与分析写在实验报告中。
问题1:红色部分的含义是什么?增加括号中的数字会发生什么?
答:程序中红色部分的作用可以让执行该语句的进程等待一秒钟后继续执行。增加数字就等于增加等待的时间。sleep方法是Thread类的静态方法,调用此方法会让当前线程暂停指定的时间,将执行机会(CPU)让给其他线程,但是不会释放锁,因此休眠时间结束后自动恢复(程序回到就绪状态)。
问题2:后台运行该程序(./程序名
&),sleep设置为10,用ps查看当前运行程序,查看进程状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BbNPfJ30-1573135215030)(media/cd681f7bf76204ff4ed23bee83415132.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4qUlUdCT-1573135215030)(media/848b9d410092156ecd7126651d5f6a95.png)]
4.使用 kill命令结束进程
用法:kill[-signal ] 进程号
执行(kill 进程号)命令,系统会发送一个SIGTERM信号给对应的程序。
执行(kill -9 进程号)命令,系统给对应程序发送的信号是SIGKILL,即exit。
例:$kill 1330
$kill -9 1330
分析kill和kill -9的区别,将分析写在实验报告中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TUd4wsND-1573135215031)(media/8e9f1c91e26771374739b629f7f7a14f.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SNFR8bHV-1573135215031)(media/57011388233c2fa710a36673b964cef9.png)]
答:Kill -9利用的是kill -Signal
pid这种方式来杀死进程。Signal是发送给进程的信号。我们可以在终端输入kill
-l来看一下都有哪些信号:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zoHaM1nX-1573135215032)(media/bf92fe4a11d6d872c3c0422784e0c0bd.png)]
答:而在本题目中的-9,可以很明显看出来发送的信号是SIGKILL,这说明此时是强制杀死该进程,而在默认参数下,也就是说不加参数直接killpid的情况下,kill发送的是15(SIGTERM)信号给进程,告诉进程,你需要被关闭,请自行停止运行并退出。与SIGTERM相比,SIGKILL信号不能被捕获或忽略,同时接收这个信号的进程在收到这个信号时不能执行任何清理。
四、思考题:
1.程序中红色部分的作用可以让执行该语句的进程等待一秒钟后继续执行,当不添加改语句时程序的运行结果是什么?为什么?
2.添加sleep(1)语句后程序的运行结果是什么?与不加该语句有何区别?为什么?
3.通过PS命令查看各个程序的状态,并分析。
五、附录
分析task_struct
在操作系统下,对一个进程管理,使用结构体来表示一个进程叫做pcb,process control
block,进程控制块。在linux下定义结构体task_struct来表示一个进程,或被成为任务结构体。
- 重要字段解释
进程描述符的结构名称叫做task_struct,在文件include/linux/sched.h中定义。
结构体定义的开始部分如下:
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ void
*stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
在代码运行运行过程中,进程的可分为不同的状态,在linux下,进程分为三种主要状态状态:就绪态、运行态和阻塞状态。每种状态将会依据外部条件不断的切换。
刚开始创建的进程叫做就绪态,但是叫做task_running
当被调用之后,开始运行叫做运行态,也叫做task_running
这两个状态都用了task_runing同一个标志,可理解为可运行状态,是否运行要看时间片等信息。
如果进程终止后进入僵尸状态,最终被回收。
如果等待某件事情将进入阻塞状态。如果被唤醒重新进入就绪态。
void *
stack表示内核栈。在代码运行的过程中需要中断关注内核的堆栈的创建过程与切换过程。
ptrace是用来跟踪线程(Linux中调度单位为线程)的,比如当进程运行出现异常时,是否回溯当前的状态,判断的依据就是这个变量,在syscall中我们就能够看到这个对这个变量进行了判断。
#ifdef CONFIG_SMP
struct llist_node wake_entry;
int on_cpu;
struct task_struct *last_wakee;
unsigned long wakee_flips;
unsigned long wakee_flip_decay_ts;
int wake_cpu;
#endif
这里是对对称多处理器的情况下代码如何设置。字面上的意思有wake_cpu表示唤醒哪一个cpu,说明了这里用来管理多cpu的运行同步相关。
int on_rq;
int prio, static_prio, normal_prio; // 进程的优先级
unsigned int rt_priority; // 实时进程的优先级
const struct sched_class *sched_class; // 调度器的指针
struct sched_entity se; // 调度器 实例化的对象
struct sched_rt_entity rt; // 实时 调度器的一个对象
on_rq表示正在正在运行的队列,running
queue的缩写。还有进程的优先级,比如静态的优先级还是正常优先级或者实时优先级。什么时候需要用到进程优先级呢?进程调度的时候判断进程优先级,这是一个重要的依据。因此下面有sched相关的定义。
struct list_head tasks;
此处是将进程以链表的形式管理起来。
struct mm_struct *mm, *active_mm;
一个进程对应着固定的虚拟内存空间但是如何映射到物理空间上,因此需要内存管理相关。
pid_t pid;
pid_t tgid;
进程号用来唯一的描述一个进程,在linux下,每一个进程都被称作轻量级的线程。tgid是为了进程派生出来线程后,进行线程管理时而作的工作。tgid的值等于第一个进程的pid,以后派生的所有的线程都具有相同的进程id,就是tgid。而每一个线程的pid是不同的,也叫做轻量级的进程。
/*
* children/sibling forms the list of my natural children
*/
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent’s children list */
struct task_struct *group_leader; /* threadgroup leader */
进程之间的关系,父进程,子进程,子进程之间形成兄弟关系。
/* CPU-specific state of this task */
struct thread_struct thread;
线程的注释是当前任务的cpu状态,下面子进程创建将会看到。
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
/* namespaces */
struct nsproxy *nsproxy;
与当前进程相关的文件描述符信息
/* signal handlers */
struct signal_struct *signal;
struct sighand_struct *sighand;
进程有关的信号处理。比如系统调用退出到用户空间的时候,就会处理信号。
#ifdef CONFIG_TRACING
/* state flags for use by tracers */
unsigned long trace;
/* bitmask and counter of trace recursion */
unsigned long trace_recursion;
#endif /* CONFIG_TRACING */
/* signal handlers */
struct signal_struct *signal;
struct sighand_struct *sighand;
进程有关的信号处理。比如系统调用退出到用户空间的时候,就会处理信号。
#ifdef CONFIG_TRACING
/* state flags for use by tracers */
unsigned long trace;
/* bitmask and counter of trace recursion */
unsigned long trace_recursion;
#endif /* CONFIG_TRACING */
进程跟踪相关。