2017-2018-1 20179203 《Linux内核原理与分析》第六周作业
一、实验五分析:
1.1 实验要求
1).使用gdb跟踪分析一个系统调用内核函数(您上周选择那一个系统调用),系统调用列表参见http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl ,推荐在实验楼Linux虚拟机环境下完成实验。
2).根据本周所学知识分析系统调用的过程,从system_call开始到iret结束之间的整个过程,并画出简要准确的流程图
1.2 实验过程:
1).首先打开Menu文件夹中,修改text.c,并且加入上周的函数getppid():
2).将menu进行编译,make rootfs:
3).检查是否成功的增加了相应的功能:
1.3 实验分析:
通过学习课程我们知道了,linux的系统调用过程:用户程序→C库(即API):INT 0x80 →system_call→系统调用服务例程→内核程序。我们常说的用户API其实就是系统提供的C库。
系统调用是通过软中断指令 INT 0x80 实现的,而这条INT 0x80指令就被封装在C库的函数中。软中断和我们常说的硬中断不同之处在于,软中断是由指令触发的,而不是由硬件外设引起的。INT 0x80 这条指令的执行会让系统跳转到一个预设的内核空间地址,它指向系统调用处理程序,即system_call函数。系统调用处理程序system_call 并不是系统调用服务例程,系统调用服务例程是对一个具体的系统调用的内核实现函数,而系统调用处理程序是在执行系统调用服务例程之前的一个引导过程,是针对INT 0x80这条指令,面向所有的系统调用的。简单来讲,执行任何系统调用,都是先通过调用C库中的函数,这个函数里面就会有软中断 INT 0x80 语句,然后转到执行系统调用处理程序 system_call ,system_call 再根据具体的系统调用号转到执行具体的系统调用服务例程。
而本次实验中我成功的将这个新的函数功能加入了原本的内核菜单中,并且学会了如何清除原来的菜单,下载并更新成为一个新的菜单,可以说学到了Linux内核菜单的修改与覆盖,但是我还是没有实现在自己的虚拟机上实现这些,qemu,git这些指令在虚拟机上都无法使用,导致编译内核这些部分都无法实现,通过今后老师的帮助,希望我的虚拟机可以实现实验楼的全部功能。
最后我做了一个流程图,总结从system_call开始到iret结束的过程:
二、学习总结
本周最重要的知识就是学习了操作系统的资源分配,明白了线程的执行情况,并且通过编程具体的看到了P,V原语的使用。下面就对本周学习到的知识进行小结。
2.1 基础知识
1).万能函数
万能函数主要是利用了void * 可以转变为任何声明类型的原理对函数的类型进行定义,从而声明所要使用的线程函数。例如:void* reader_thread(void * arg)。从这个函数声明中可以看到,函数的类型和变量的类型均使用void * 的手法进行声明,以此使得任意类型的线程都可以用相同的pthread_create线程创建函数创建。
2).临界区与临界资源
首先再次申明一下临界区和临界资源的概念。
临界区:每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,不允许其他进程进入。多个进程必须互斥的对它进行访问。
临界资源:各进程采取互斥的方式,实现共享的资源称作临界资源。临界资源是一次仅允许一个进程将使用的共享资源。
通过本次设计的读者写者问题对这一概念做出更加清晰的解释。本次一共分为两个进程读者进程和写者进程,一块内存的共享资源,读者读取其中的信息,写者向其中写入信息。而正在有人写东西的时候肯定不允许有人阅读其中的内容,因为还未写完,读到的内容会出错,因此读者和写者之间对于这块区域的访问应当是互斥的,即有人读时不能写,有人写时不能读,因此两者之间需要一个临界资源。其次当有写者正在进行写的时候其他写者不能同时向其中写入,这样就会覆盖出错,因此写者与写者之间互斥,即任一写者写操作时其他写者等待,因此写者之间同样存在临界资源。
3).P,V原语
PV原语通过操作信号量来处理进程间的同步与互斥的问题。其核心就是一段不可分割不可中断的程序。通过P原语使得临界资源的数量减一,比如读写操作之间互斥,因此有一个临界资源,当读线程进入临界区时,就需要对临界资源进行P操作,这样此时临界资源为0,当写线程到来时再对临界资源进行P操作时发现临界资源已经为0,就无法进入临界区,只有当读线程执行完毕,执行V原语释放临界资源,此时的写线程才能抢到临界资源,进入临界区。
2.2 示例代码分析
#define READER_MAX 3
#define WRITER_MAX 2
int buffer;
首先我定义了读线程和写线程的数量,读线程3个,写线程两个并且我定义了一个读写访问的数据区。
pthread_rwlock_t rw_lock;
这个是至关重要的关于读写者问题的一个定义读写锁,按我自己的理解这个就是C语言库对于读写问题临界资源,以及其中PV原语操作已经定义好的结构体。至于具体的使用下面详细分析。
void* reader_thread(void *arg)
{
static int c = -1;
int i;
while(1) {
c = (c+1) % READER_MAX;//线程号的分配
if(pthread_rwlock_tryrdlock(&rw_lock)) {
printf("读者%u暂时不能读取数据.\n", c);
sleep(2);
} else {
i=c;
printf("读者%u正在读取数据:%d.\n", c,buffer);
sleep(1);
printf("读者%u读取数据完毕.\n", i);
pthread_rwlock_unlock(&rw_lock);
sleep(2);
}
}
}
这是读线程的程序,由buffer的数据区中读出相应的数据,其中需要重点强调的两个语句是pthread_rwlock_tryrdlock(&rw_lock)与 pthread_rwlock_unlock(&rw_lock)。这是读写锁锁定与解锁的操作语句,也就是PV原语。对于读线程的PV原语我认为设计的非常的巧妙,pthread_rwlock_tryrdlock(&rw_lock)这条语句代表着当读线程到来时如果当前没有线程或是读线程占用临界资源则锁定读写锁,而由读线程锁定的读写锁只不允许写线程进入临界区而对读线程不做限制,每个读线程到来时都会对读写锁进行一次锁定。当读取完成后pthread_rwlock_unlock(&rw_lock)解除锁定,只有当所有锁定过的读线程进行了解锁,才会真的解锁读写锁。
void* writer_thread(void *arg)
{
static int p = -1;
int i;
while(1) {
p = (p+1) % WRITER_MAX;
if(pthread_rwlock_trywrlock(&rw_lock)) {
printf("写者%u暂时不能写入数据\n", p);
sleep(2);
}
else {
i=p;
buffer = rand() % 1000;
printf("写者%u正在写入数据:%d.\n", p, buffer);
sleep(2);
printf("写者%u写入数据完毕.\n", i);
pthread_rwlock_unlock(&rw_lock);
sleep(3);
}
}
}
这是写线程的函数,其中pthread_rwlock_trywrlock(&rw_lock)与pthread_rwlock_unlock(&rw_lock)是写线程的PV原语,但是其pthread_rwlock_trywrlock(&rw_lock)P原语与读线程最大的不同是当写线程锁定读写锁后,所有其他读写进程均不能访问临界区,只有当当前的进程完成写操作,进行解锁后才允许其他等待进程访问。
2.3 完整程序代码与运行结果
运行结果:
文字打印的时间还是要长于线程抢占临界资源执行的时间的,所以有些输出的结果显示不能尽如人意。
完整代码:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#define READER_MAX 3
#define WRITER_MAX 2
int buffer;
pthread_rwlock_t rw_lock;
void* reader_thread(void *arg)
{
static int c = -1;
int i;
while(1) {
c = (c+1) % READER_MAX;
if(pthread_rwlock_tryrdlock(&rw_lock)) {
printf("读者%u暂时不能读取数据.\n", c);
sleep(2);
} else {
i=c;
printf("读者%u正在读取数据:%d.\n", c,buffer);
sleep(1);
printf("读者%u读取数据完毕.\n", i);
pthread_rwlock_unlock(&rw_lock);
sleep(2);
}
}
}
void* writer_thread(void *arg)
{
static int p = -1;
int i;
while(1) {
p = (p+1) % WRITER_MAX;
if(pthread_rwlock_trywrlock(&rw_lock)) {
printf("写者%u暂时不能写入数据\n", p);
sleep(2);
}
else {
i=p;
buffer = rand() % 1000;
printf("写者%u正在写入数据:%d.\n", p, buffer);
sleep(2);
printf("写者%u写入数据完毕.\n", i);
pthread_rwlock_unlock(&rw_lock);
sleep(3);
}
}
}
int main(int argc, char* argv[])
{
pthread_t reader, writer;
int i = 0;
pthread_rwlock_init(&rw_lock, NULL);
for(i = 0; i < WRITER_MAX; i++)
pthread_create(&writer, NULL, (void *)writer_thread, NULL);//创建所需数量的线程
for(i = 0; i < READER_MAX; i++)
pthread_create(&reader, NULL, (void *)reader_thread, NULL);
sleep(10);//执行时间为10S
return 0;
}
三、未解决的问题;
通过这次读者写者的问题编写让我了解到了为解决该问题所产生的读写锁,但是与之而来的一个问题就是,真正计算机中可被读写的内存绝不像问题中仅有一块而已,如果写进程来到锁定了读写锁,那么其他读写进程想要访问其他区域也被禁止了吗?肯定是不会的,那么是每一块可被访问的读写区域都有一个相应的读写锁吗?还是说该PV原语还有其他形式的高级应用可以区分线程之间的访问区域,只禁止访问同一区域的线程?真正的编程中又是怎样具体实现的呢?