武汉大学操作系统大作业_2_操作系统武汉大学
武汉大学操作系统大作业_2由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“操作系统武汉大学”。
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_proce(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 子线程定义
运行结果3.12、心得体会
通过此次学习,我对进程、线程的概念有了进一步的认识,初步了解了fork、pthread的原理以及使用方法,但是此次实验仍有不足的地方。
(1)相关知识不足,在看fork和pthread源代码的时候有很多不明白的地方,尤其是pthread部分。
(2)对部分代码的运行结果不能很好的理解。
(3)代码中没有实现打印处理器使用、内存使用等基本信息。
四、附件
//源代码
// Created by Yi Mingli on 16/4/1.// Copyright(c)2016年 Yi Mingli.All rights reserved.#include #include #include #include #include
#define THREAD_NUM 2 pthread_t threads[THREAD_NUM];//线程返回数组 pthread_mutex_t mut;int number=0;void *thread2(){ printf(“I am thread2,my PID is %d,and my id is %lu(0x%x)n”,getpid(),pthread_self(),getpid());pthread_exit(NULL);return NULL;} void *thread1(){ printf(“I am thread1,my PID is %d,and my id is %lu(0x%x)n”,getpid(),pthread_self(),getpid());int sthread=pthread_create(&threads[1], NULL,(void*)thread2, NULL);//创建线程2 if(sthread){ printf(“error in thread2!n”);} if(threads[1]!=0){ pthread_join(threads[1], NULL);printf(“thread2 is over!n”);} pthread_exit(NULL);return NULL;} int main(int argc, const char * argv[]){ pid_t fpid=fork();//创建第一个子进程 if(fpid
} //进程创建失败 else if(fpid==0)//子进程 { printf(“I am proce2,my father proce is %d,my id is %d,and the value of fpid is %d(0x%x)n”,getppid(),getpid(),fpid,getpid());pid_t sfpid=fork();//创建第二个进程 if(sfpid