2017-2018-1 20179215《Linux内核原理与分析》第六周作业
一、实验部分:使用gdb跟踪分析一个系统调用内核函数(上周选择的那一个系统调用)。
【第一部分】 根据要求完成第一部分,步骤如下:
①更新menu代码到最新版
②在原有代码中加入C函数、汇编函数
int Getuid(int argc,char *argv[])
{
int uid;
uid=getuid();
printf("uid=%d\n",uid);
return 0;
}
int GetuidAsm()
{
int uid;
uid=getuid();
asm volatile(
"mov $0,%%ebx\n\t"
"mov $0x18,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
:"=m"(uid)
);
printf("uid=%d\n",uid);
return 0;
}
③在main函数中加入getuid以及getuid-asm的makeconfig
MenuConfig("getuid","Show System User",Getuid);
MenuConfig("getuid_asm","Show System User(asm)",GetuidAsm);
④make rootfs
⑤可以看到qemu中增加了我们先前添加的命令
⑥分别执行新增的命令
【第二部分】gdb跟踪分析一个系统调用内核函数
①进入gdb调试
②设置断点,继续执行,得到结果:
③查看我所选用的系统调用的函数:
④设置断点在sys_getuid16处,发现执行命令getuid时并没有停下:
⑤反而在执行getuid_asm时停下了:
⑥直接结束若干次单步执行,然后继续往下单步执行,发现出现了进程调度函数,返回了进程调度中的一个当前进程任务的值。
⑦设置断点于system_ call处。发现可停,而继续执行时,刚才停下的getuid_asm也返回了值。
【第三部分】system_call到iret过程
系统调用在内核代码中的工作机制和初始化
1.系统调用机制的初始化
trap_init();
#ifdef CONFIG_X86_32
set_ system_trap_ gate(SYSCALL_VECTOR,&system_call);//两个参数分别代表:系统调用的中断向量;汇编代码的入口,一旦执行时int 0x80,系统就跳转至此执行
set_bit(SYSCALL_VECTOR,used-vectors);
#endif
2.理解system_call代码
# system call handler stub
ENTRY(system_call)
RING0_INT_FRAME # can't unwind into user space anyway
ASM_CLAC
pushl_cfi %eax # save orig_eax
SAVE_ALL // 保存系统寄存器信息,即保存现场
GET_THREAD_INFO(%ebp) // 获取thread_info结构的信息
# system call tracing in operation / emulation
testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) // 测试是否有系统跟踪
jnz syscall_trace_entry // 如果有系统跟踪,先执行,然后再回来
cmpl $(NR_syscalls), %eax // 比较eax中的系统调用号和最大syscall,超过则无效
jae syscall_badsys // 无效的系统调用 直接返回
syscall_call:
call *sys_call_table(,%eax,4) // 调用实际的系统调用程序
syscall_after_call:
movl %eax,PT_EAX(%esp) // 将系统调用的返回值eax存储在栈中
syscall_exit:
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
testl $_TIF_ALLWORK_MASK, %ecx //检测是否所有工作已完成
jne syscall_exit_work //工作已经完成,则去进行系统调用推出工作
restore_all:
TRACE_IRQS_IRET // iret 从系统调用返回
System_ Call中的关键部分:syscall_ exit_ work
syscall_exit_work:
testl $_TIF_WORK_SYSCALL_EXIT, %ecx //测试syscall的工作完成
jz work_pending
TRACE_IRQS_ON //切换中断请求响应追踪可用
ENABLE_INTERRUPTS(CLBR_ANY) # could let syscall_trace_leave() call
//schedule() instead
movl %esp, %eax
call syscall_trace_leave //停止追踪系统调用
jmp resume_userspace //返回用户空间,只需要检查need_resched
END(syscall_exit_work)
该过程为系统调用完成后如何退出调用的过程,其中比较重要的是work_pending,详见如下:
work_pending:
testb $_TIF_NEED_RESCHED, %cl // 判断是否需要调度
jz work_notifysig // 不需要则跳转到work_notifysig
work_resched:
call schedule // 调度进程
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx // 是否所有工作都已经做完
jz restore_all // 是则退出
testb $_TIF_NEED_RESCHED, %cl // 测试是否需要调度
jnz work_resched // 重新执行调度代码
work_notifysig: // 处理未决信号集
#ifdef CONFIG_VM86
testl $X86_EFLAGS_VM, PT_EFLAGS(%esp) // 判断是否在虚拟8086模式下
movl %esp, %eax
jne work_notifysig_v86 // 返回到内核空间
1:
#else
movl %esp, %eax
#endif
TRACE_IRQS_ON // 启动跟踪中断请求响应
ENABLE_INTERRUPTS(CLBR_NONE)
movb PT_CS(%esp), %bl
andb $SEGMENT_RPL_MASK, %bl
cmpb $USER_RPL, %bl
jb resume_kernel // 恢复内核空间
xorl %edx, %edx
call do_notify_resume // 将信号投递到进程
jmp resume_userspace // 恢复用户空间
#ifdef CONFIG_VM86
ALIGN
work_notifysig_v86:
pushl_cfi %ecx # save ti_flags for do_notify_resume
call save_v86_state // 保存VM86模式下的CPU信息
popl_cfi %ecx
movl %eax, %esp
jmp 1b
#endif
END(work_pending)
System_Call的基本处理流程为:
首先保存中断上下文(SAVE_ALL,也就是CPU状态,包括各个寄存器),判断请求的系统调用是否有效,然后call *sys_call_table(,%eax,4)通过系统查询系统调用查到相应的系统调用程序地址,执行相应的系统调用,系统调用完后,返回系统调用的返回值,关闭中断响应,检测系统调用的所有工作是否已经完成,如果完成则进行syscall_ exit_ work(完成系统调用退出工作),最后restore_all(恢复中断请求响应),返回用户态
总结:具体的系统调用与系统调用号绑定,然后都记载在一个系统调用表内,每次使用系统调用时都是通过这样的绑定关系,由系统调用号去找系统调用表然后查找到所对应的系统调用的位置。同理,中断处理过程也是一样的,它也是经由中断向量号作为索引去查表,然后执行相应的具体的中断处理程序去处理中断。简而言之就是“两个号&两张表”。
整体的流程图:
存在的疑问: |
###看到在执行work_ pending()后才又重新开启总中断,那么在work_pending函数中判断的是否有被阻塞的信号指什么时候进来的信号,为什么不等返回用户之后再来处理这些信号?
二、读书笔记
1、同步 |
所谓同步,其实防止在临界区中形成竞争条件。如果临界区里是原子操作(即整个操作完成前不会被打断),那么自然就不会出竞争条件。但在实际应用中,临界区中的代码往往不会那么简单,所以为了保持同步,引入了锁机制。
2、互斥量与信号量 |
互斥量如其名,同一时间只能被一个线程占有,实现线程间对某种数据结构的互斥访问。试图对一个已经加锁的互斥量加锁,会导致线程阻塞。允许多个线程对同一个互斥量加锁。当对互斥量解锁时,阻塞在该互斥量上的线程会被唤醒,它们竞争对该互斥量加锁,加锁成功的线程将停止阻塞,剩余的加锁失败于是继续阻塞。注意到,谁将竞争成功是无法预料的,这一点就类似于弱信号量。(强信号量把阻塞在信号量上的进程按时间排队,先进先出)
互斥量区别于信号量的地方在于,互斥量只有两种状态,锁定和非锁定。它不像信号量那样可以赋值,甚至可以是负值。共性方面,我所体会到的就一句话,都是用来实现互斥的。
1、生产者消费者问题
该问题要满足:
(1). 当缓冲区已满时,生产者不会继续向其中添加数据
(2). 当缓冲区为空时,消费者不会从中移走数据
(3). 要避免忙等待,睡眠和唤醒操作(原语)
#define N 100 /*缓冲区个数*/
typedef int semaphore; /*信号量是一种特殊的整数类型*/
semaphore mutex = 1; /*互斥信号量:控制对临界区的访问*/
semaphore empty= N; /*空缓冲区的个数*/
semaphore full = 0; /*满缓冲区个数*/
void producer(void)
{
int item;
while(TRUE)
{
item = produce_item()
P(&empty);
P(&mutex);
insert_item(item);
V(&mutex);
V(&full);
}
}
void consumer(void)
{
int item;
while(TRUE)
{
P(&full);
P(&mutex);
item = remove_item()
P(&mutex);
V(&empty);
consume_item(item);
}
}
2、读者写者问题
有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作;②只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。
(1)读者优先 |
读进程是优先的,也就是说,当存在读进程时,写操作将被延迟,并且只要有一个读进程活跃,随后而来的读进程都将被允许访问文件。在这种方式下,会导致写进程可能长时间等待,导致存在写进程“饿死”的情况。
int count=0; //用于记录当前的读者数量
semaphore mutex=1; //用于保护更新count变量时的互斥
semaphore rw=1; //用于保证读者和写者互斥地访问文件
writer () { //写者进程
while (1){
P(rw); // 互斥访问共享文件
Writing; //写入
V(rw) ; //释放共享文件
}
}
reader () { // 读者进程
while(1){
P (mutex) ; //互斥访问count变量
if (count==0) //当第一个读进程读共享文件时
P(rw); //阻止写进程写
count++; //读者计数器加1
V (mutex) ; //释放互斥变量count
reading; //读取
P (mutex) ; //互斥访问count变量
count--; //读者计数器减1
if (count==0) //当最后一个读进程读完共享文件
V(rw) ; //允许写进程写
V (mutex) ; //释放互斥变量 count
}
}
(2)写者优先 |
即当有读进程正在读共享文件时,有写进程请求访问,这时应禁止后续读进程的请求,等待到已在共享文件的读进程执行完毕则立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次运行。为此,增加一个信号量并且在上面的程序中 writer()和reader()函数中各增加一对PV操作,就可以得到写进程优先的解决程序。
int count = 0; //用于记录当前的读者数量
semaphore mutex = 1; //用于保护更新count变量时的互斥
semaphore rw=1; //用于保证读者和写者互斥地访问文件
semaphore w=1; //用于实现“写优先”
writer(){
while(1){
P(w); //在无写进程请求时进入
P(rw); //互斥访问共享文件
writing; //写入
V(rw); // 释放共享文件
V(w) ; //恢复对共享支件的访问
}
}
reader () { //读者进程
while (1){
P (w) ; // 在无写进程请求时进入
P (mutex); // 互斥访问count变量
if (count==0) //当第一个读进程读共享文件时
P(rw); //阻止写进程写
count++; //读者计数器加1
V (mutex) ; //释放互斥变量count
V(w); //恢复对共享文件的访问
reading; //读取
P (mutex) ; //互斥访问count变量
count--; //读者计数器减1
if (count==0) //当最后一个读进程读完共享文件
V(rw); //允许写进程写
V (mutex); //释放互斥变量count
}
}
3、死锁 |
死锁就是所有线程都在相互等待释放资源,导致谁也无法继续执行下去。比如哲学家进餐问题,当每个人都同时拿起左边的筷子,那么同时每个人都在等待有人放下获取另一支筷子,这时就构成了死锁。可见竞争资源以及推进进程顺序不当会引发死锁。下面一些简单的规则可以帮助我们避免死锁:
1. 如果有多个锁的话,尽量确保每个线程都是按相同的顺序加锁,按加锁相反的顺序解锁。
(即加锁a->b->c,解锁c->b->a)
2. 防止发生饥饿。即设置一个超时时间,防止一直等待下去。
3. 不要重复请求同一个锁。
4. 设计应力求简单。加锁的方案越复杂就越容易出现死锁。
对于解决哲学家进餐问题有两种解决办法:
(1)同时只允许一位哲学家就餐 |
semaphore fork[5]={1,1,1,1,1};
semaphore mutex = 1;
void philosopher(int i){
while(TRUE){
think();
P(mutex)
P(fork[i]);
P(fork[(i+1)%N);
V(mutex)
eat();
V(fork[i]);
V(fork[(i+1)%N];
}
(2)对哲学家顺序编号,要求奇数号哲学家先抓左边的叉子,然后再抓他右边的叉子,而偶数号哲学家刚好相反。 |
semaphore fork[5]={1,1,1,1,1};
void philosopher(int i){
while(TRUE){
think();
if(i%2==1){
P(fork[i]);
P(fork[(i+1)%N);
}else{
P(fork[(i+1)%N]);
P(fork[i]);
}
eat();
V(fork[i]);
V(fork[(i+1)%N];
}
2017-2018-1 20179215《Linux内核原理与分析》第六周作业的更多相关文章
- 2017-2018-1 20179215《Linux内核原理与分析》第二周作业
20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...
- 20169212《Linux内核原理与分析》第二周作业
<Linux内核原理与分析>第二周作业 这一周学习了MOOCLinux内核分析的第一讲,计算机是如何工作的?由于本科对相关知识的不熟悉,所以感觉有的知识理解起来了有一定的难度,不过多查查资 ...
- 20169210《Linux内核原理与分析》第二周作业
<Linux内核原理与分析>第二周作业 本周作业分为两部分:第一部分为观看学习视频并完成实验楼实验一:第二部分为看<Linux内核设计与实现>1.2.18章并安装配置内核. 第 ...
- 2018-2019-1 20189221 《Linux内核原理与分析》第九周作业
2018-2019-1 20189221 <Linux内核原理与分析>第九周作业 实验八 理理解进程调度时机跟踪分析进程调度与进程切换的过程 进程调度 进度调度时机: 1.中断处理过程(包 ...
- 2019-2020-1 20199329《Linux内核原理与分析》第九周作业
<Linux内核原理与分析>第九周作业 一.本周内容概述: 阐释linux操作系统的整体构架 理解linux系统的一般执行过程和进程调度的时机 理解linux系统的中断和进程上下文切换 二 ...
- 2019-2020-1 20199329《Linux内核原理与分析》第二周作业
<Linux内核原理与分析>第二周作业 一.上周问题总结: 未能及时整理笔记 Linux还需要多用 markdown格式不熟练 发布博客时间超过规定期限 二.本周学习内容: <庖丁解 ...
- 2019-2020-1 20209313《Linux内核原理与分析》第二周作业
2019-2020-1 20209313<Linux内核原理与分析>第二周作业 零.总结 阐明自己对"计算机是如何工作的"理解. 一.myod 步骤 复习c文件处理内容 ...
- 2018-2019-1 20189221《Linux内核原理与分析》第一周作业
Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...
- 《Linux内核原理与分析》第一周作业 20189210
实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...
- 2018-2019-1 20189221《Linux内核原理与分析》第二周作业
读书报告 <庖丁解牛Linux内核分析> 第 1 章 计算工作原理 1.1 存储程序计算机工作模型 1.2 x86-32汇编基础 1.3汇编一个简单的C语言程序并分析其汇编指令执行过程 因 ...
随机推荐
- Android拍照生成缩略图
在Android 2.2版本中,新增了一个ThumbnailUtils工具类来是实现缩略图,此工具类的功能是强大的,使用是简单,它提供了一个常量和三个方法.利用这些常数和方法,可以轻松快捷的实现图片和 ...
- 【BZOJ1444】[Jsoi2009]有趣的游戏 AC自动机+概率DP+矩阵乘法
[BZOJ1444][Jsoi2009]有趣的游戏 Description Input 注意 是0<=P Output Sample Input Sample Output HINT 30%的 ...
- WCF基础之消息协定
通常定义消息的架构,使用数据协定就够了,但是有时必须将类型精确映射到soap消息,方法两种:1.插入自定义soap标头:2.另一种是定义消息的头和正文的安全属性.消息协定通过MessageContra ...
- simple -- abstract
<?php abstract class Operation { protected $_NumberA = 0; protected $_NumberB = 0; protected $_Re ...
- static_func
<?php function testing() { static $a = 1; $a *= 2; echo $a."\n"; } testing(); testing() ...
- QCon2016 上海会议汇总(2) - 团队管理
QCon 2016上海日程:http://2016.qconshanghai.com/schedule <当你的团队还支撑不起梦想时> - 链尚网技术合伙人 杨荣伟 Figo讲述了如何训练 ...
- Python list,tuple,dict and set
list 有序可变的集合 查找和插入的时间随着元素的增加而增加 占用空间小,浪费内存很少 tuple 有序只读不可变.因为tuple不可变,所以代码更安全.如果可能,能用tuple代替list就尽量用 ...
- spring装配机制
spring容器创建bean并通过DI(依赖注入)来协调他们之间的关系,他有三种装配机制: 1. 在XML中显式配置 2. 在Java文件中显式配置 3. 隐式的bean发现机制(组件扫描)和自动装配 ...
- Recovery模式【转】
本文转载自:http://tieba.baidu.com/p/2299027486 Recovery模式是手机系统的一个工程模式,作用是恢复和清除.用户进入这个模式之后,可以对当前系统的一些数据进行清 ...
- 【转】如何在html与delphi间交互代码
[转]如何在html与delphi间交互代码 (2015-11-19 22:16:24) 转载▼ 标签: it 分类: uniGUI uniGUI总群中台中cmj朋友为我们总结了如下内容,对于利用de ...