武汉大学操作系统大作业 - 2

更新时间:2023-11-09 03:53:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Fork、Pthread实验报告

一、学习目标

1.学习fork函数和pthread函数的使用,阅读源码,分析两个函数的机理。 2.在系统中创建一个三层次父子进程树,并具有两层次线程,并打印运行中各个执行体的处理器使用、内存使用等基本信息。

二、基本原理

1、fork函数

fork函数的函数原型是pid_t fork( void)。fork()函数的响应函数是 sys_fork()、sys_clone()、sys_vfork()。这三个函数都是通过调用内核函数 do_fork() 来实现的。

使用do_fork()函数创建一个进程大致分为如下几个过程:

(1)向系统申请在内存中分配一个 task_struct 数据结构,即进程控制块PCB, do_fork()中通过使用alloc_task_struct()实现。task_struct是LINUX内核用以管理进程的结构体,它包含了进程状态、PID、内核栈等等执行进程所必须要的资源。

(2)对PCB进行初始化操作。通过执行*p=*curren,将父进程(当前进程)的PCB内容拷贝到新进程中去,重新设置 task_struct 结构中那些与父进程值不同的数据成员,为进程分配标志号。根据参数中传入的 clone_flags 参数值,决定是否要拷贝父进程 task_struct 中的指针 fs 、files 指针等所选择的部分。

(3)将新进程加入到进程链表中去,并拷贝父进程的上下文来初始化子进程上下文。启动调度程序,通过wake_up_process(p)唤醒子进程,并放入就绪队列当中。父进程返回子进程的PID,子进程返回0。

通过do_fork()函数以及示例代码运行结果,可以了解到fork()函数的如下特点: (1)fork函数返回值Pid_t 是在头文件sys/types.h中定义的宏,在调用fork后会返回两个值,如果是子进程则返回值为0,如果是父进程则返回值大于0(为子进程的PID),如果创建进程失败则返回值小于0。

(2)通过fork函数创建的新进程是对父进程的复制,子进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致。通过示例代码1.1可以看见父、子进程最后执行结果中count的值都是1,这是因为子进程拷贝的是父进程当前的状态, count为0,执行了‘++’操作变为1。

(3)父进程和子进程的概念是相对的,如运行结果1.2所示,p722是p719的子进程,也是p724的父进程。父进程和子进程的执行顺序是不一定的,这依赖于系统调度。子进程和父进程的可执行程序是同一个程序、上下文和数据,绝大部分就是原进程(父进程)的拷贝,但它们是两个相互独立的进程,fork()以后,子进程就相当于父进程的兄弟一样了。

(4)在运行结果1.1中可以看见子进程输出时父进程的ID为1,这是因为在子进程执行printf语句的时候,父进程已经结束了,对于没有父进程的子进程系统会将其父进程置为init(PID为1)进程。

代码示例1.1

运行结果1.1

示例代码1.2

运行结果1.2

【注】 代码示例1.1运行结果如运行结果1.1所示,示例代码1.2运行结果如运行结果1.2所示。

2、pthread函数

Pthread中定义了一系列线程操作函数、同步互斥函数,此次主要学习了pthread_create()、pthread_join()、pthread_exit()函数。

(1)pthread_create()函数用于线程创建,第一个参数pthread_t *thread用于存储创建成功的线程的ID。第二个参数const pthread_attr_t * attr 用于给定线程属性,置为NULL则使用默认属性。

pthread_create()源代码中,新进程的参数、信息均被打包到request结构中,再通过__libc_write(__pthread_manager_request, (char *) &request, sizeof(request)),将请求写入全局变量__pthread_manager_request,最后由__pthread_manager()函数创建并管理线程。

(2)pthread_join()函数用于挂起线程,第一个参数pthread_t thread,用于指定次需要等待结束的线程,当thread指定的线程没有终止,将导致调用线程挂起,直到由参数thread指定的线程终止。最后一个参数void**status是通过pthread_exit()函数传递进来的,如作业代码中主线程要等待线程1结束,而线程1要等待线程2结束。 (3)pthread_exit()函数用于结束线程,参数status将子线程的结束状态传递给主线程。主线程可以在pthread_join()成功返回后获得子线程的结束状态,并根据不同的结束状态做不同的处理。

通过运行示例代码可以发现线程的一些特点:

(1)子线程的创建顺序与子线程执行顺序是不一致,子线程的执行顺序是随机的,依赖于操作系统的调度。如运行结果2.1.1和2.1.2所示,两个子线程执行顺序是随机的。

(2)示例代码2.1中,线程1线程2对number都有写操作,所以在修改number值的时候要进行加锁和解锁的操作,避免出现结果不唯一结果。如果去掉互斥锁,可以看见如运行结果2.1.2所示number值出现了错误。

(3)线程没有独立的存储空间,同一进程下的线程共享存储空间,所以在运行结果2.1.1中我们看到number的结果是逐渐累加的,在作业代码运行结果中也可以看到线程1、2与创建他们的进程共用一个内存地址,且PID均为父进程的PID。其实,线程有各自的线程ID,可以通过pthread_self()获得。

示例代码2.1两个子线程

运行结果2.1.1

运行结果2.1.2

【注】 代码示例2.1运行结果如运行结果2.1.1和2.1.2所示。

三、实验结果

1、代码及运行结果截图

作业代码 3.1.1 第一次fork

作业代码 3.1.2 第二次fork

作业代码 3.1.3 子线程定义

本文来源:https://www.bwwdw.com/article/t5yv.html

Top