一、实验部分:使用gdb跟踪分析一个系统调用内核函数(上周选择的那一个系统调用)。

【第一部分】 根据要求完成第一部分,步骤如下:

①更新menu代码到最新版

②在原有代码中加入C函数、汇编函数

  1. int Getuid(int argc,char *argv[])
  2. {
  3. int uid;
  4. uid=getuid();
  5. printf("uid=%d\n",uid);
  6. return 0;
  7. }
  8. int GetuidAsm()
  9. {
  10. int uid;
  11. uid=getuid();
  12. asm volatile(
  13. "mov $0,%%ebx\n\t"
  14. "mov $0x18,%%eax\n\t"
  15. "int $0x80\n\t"
  16. "mov %%eax,%0\n\t"
  17. :"=m"(uid)
  18. );
  19. printf("uid=%d\n",uid);
  20. return 0;
  21. }

③在main函数中加入getuid以及getuid-asm的makeconfig

  1. MenuConfig("getuid","Show System User",Getuid);
  2. 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.系统调用机制的初始化

  1. trap_init();
  2. #ifdef CONFIG_X86_32
  3. set_ system_trap_ gate(SYSCALL_VECTOR,&system_call);//两个参数分别代表:系统调用的中断向量;汇编代码的入口,一旦执行时int 0x80,系统就跳转至此执行
  4. set_bit(SYSCALL_VECTOR,used-vectors);
  5. #endif

2.理解system_call代码

  1. # system call handler stub
  2. ENTRY(system_call)
  3. RING0_INT_FRAME # can't unwind into user space anyway
  4. ASM_CLAC
  5. pushl_cfi %eax # save orig_eax
  6. SAVE_ALL // 保存系统寄存器信息,即保存现场
  7. GET_THREAD_INFO(%ebp) // 获取thread_info结构的信息
  8. # system call tracing in operation / emulation
  9. testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) // 测试是否有系统跟踪
  10. jnz syscall_trace_entry // 如果有系统跟踪,先执行,然后再回来
  11. cmpl $(NR_syscalls), %eax // 比较eax中的系统调用号和最大syscall,超过则无效
  12. jae syscall_badsys // 无效的系统调用 直接返回
  13. syscall_call:
  14. call *sys_call_table(,%eax,4) // 调用实际的系统调用程序
  15. syscall_after_call:
  16. movl %eax,PT_EAX(%esp) // 将系统调用的返回值eax存储在栈中
  17. syscall_exit:
  18. LOCKDEP_SYS_EXIT
  19. DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
  20. # setting need_resched or sigpending
  21. # between sampling and the iret
  22. TRACE_IRQS_OFF
  23. movl TI_flags(%ebp), %ecx
  24. testl $_TIF_ALLWORK_MASK, %ecx //检测是否所有工作已完成
  25. jne syscall_exit_work //工作已经完成,则去进行系统调用推出工作
  26. restore_all:
  27. TRACE_IRQS_IRET // iret 从系统调用返回

System_ Call中的关键部分:syscall_ exit_ work

  1. syscall_exit_work:
  2. testl $_TIF_WORK_SYSCALL_EXIT, %ecx //测试syscall的工作完成
  3. jz work_pending
  4. TRACE_IRQS_ON //切换中断请求响应追踪可用
  5. ENABLE_INTERRUPTS(CLBR_ANY) # could let syscall_trace_leave() call
  6. //schedule() instead
  7. movl %esp, %eax
  8. call syscall_trace_leave //停止追踪系统调用
  9. jmp resume_userspace //返回用户空间,只需要检查need_resched
  10. END(syscall_exit_work)

该过程为系统调用完成后如何退出调用的过程,其中比较重要的是work_pending,详见如下:

  1. work_pending:
  2. testb $_TIF_NEED_RESCHED, %cl // 判断是否需要调度
  3. jz work_notifysig // 不需要则跳转到work_notifysig
  4. work_resched:
  5. call schedule // 调度进程
  6. LOCKDEP_SYS_EXIT
  7. DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
  8. # setting need_resched or sigpending
  9. # between sampling and the iret
  10. TRACE_IRQS_OFF
  11. movl TI_flags(%ebp), %ecx
  12. andl $_TIF_WORK_MASK, %ecx // 是否所有工作都已经做完
  13. jz restore_all // 是则退出
  14. testb $_TIF_NEED_RESCHED, %cl // 测试是否需要调度
  15. jnz work_resched // 重新执行调度代码
  16. work_notifysig: // 处理未决信号集
  17. #ifdef CONFIG_VM86
  18. testl $X86_EFLAGS_VM, PT_EFLAGS(%esp) // 判断是否在虚拟8086模式下
  19. movl %esp, %eax
  20. jne work_notifysig_v86 // 返回到内核空间
  21. 1:
  22. #else
  23. movl %esp, %eax
  24. #endif
  25. TRACE_IRQS_ON // 启动跟踪中断请求响应
  26. ENABLE_INTERRUPTS(CLBR_NONE)
  27. movb PT_CS(%esp), %bl
  28. andb $SEGMENT_RPL_MASK, %bl
  29. cmpb $USER_RPL, %bl
  30. jb resume_kernel // 恢复内核空间
  31. xorl %edx, %edx
  32. call do_notify_resume // 将信号投递到进程
  33. jmp resume_userspace // 恢复用户空间
  34. #ifdef CONFIG_VM86
  35. ALIGN
  36. work_notifysig_v86:
  37. pushl_cfi %ecx # save ti_flags for do_notify_resume
  38. call save_v86_state // 保存VM86模式下的CPU信息
  39. popl_cfi %ecx
  40. movl %eax, %esp
  41. jmp 1b
  42. #endif
  43. 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). 要避免忙等待,睡眠和唤醒操作(原语)

  1. #define N 100 /*缓冲区个数*/
  2. typedef int semaphore; /*信号量是一种特殊的整数类型*/
  3. semaphore mutex = 1; /*互斥信号量:控制对临界区的访问*/
  4. semaphore empty= N; /*空缓冲区的个数*/
  5. semaphore full = 0; /*满缓冲区个数*/
  6. void producer(void)
  7. {
  8. int item;
  9. while(TRUE)
  10. {
  11. item = produce_item()
  12. P(&empty);
  13. P(&mutex);
  14. insert_item(item);
  15. V(&mutex);
  16. V(&full);
  17. }
  18. }
  19. void consumer(void)
  20. {
  21. int item;
  22. while(TRUE)
  23. {
  24. P(&full);
  25. P(&mutex);
  26. item = remove_item()
  27. P(&mutex);
  28. V(&empty);
  29. consume_item(item);
  30. }
  31. }

2、读者写者问题

 有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作;②只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。

(1)读者优先

 读进程是优先的,也就是说,当存在读进程时,写操作将被延迟,并且只要有一个读进程活跃,随后而来的读进程都将被允许访问文件。在这种方式下,会导致写进程可能长时间等待,导致存在写进程“饿死”的情况。

  1. int count=0; //用于记录当前的读者数量
  2. semaphore mutex=1; //用于保护更新count变量时的互斥
  3. semaphore rw=1; //用于保证读者和写者互斥地访问文件
  4. writer () { //写者进程
  5. while (1){
  6. P(rw); // 互斥访问共享文件
  7. Writing; //写入
  8. V(rw) ; //释放共享文件
  9. }
  10. }
  11. reader () { // 读者进程
  12. while(1){
  13. P (mutex) ; //互斥访问count变量
  14. if (count==0) //当第一个读进程读共享文件时
  15. P(rw); //阻止写进程写
  16. count++; //读者计数器加1
  17. V (mutex) ; //释放互斥变量count
  18. reading; //读取
  19. P (mutex) ; //互斥访问count变量
  20. count--; //读者计数器减1
  21. if (count==0) //当最后一个读进程读完共享文件
  22. V(rw) ; //允许写进程写
  23. V (mutex) ; //释放互斥变量 count
  24. }
  25. }

(2)写者优先

 即当有读进程正在读共享文件时,有写进程请求访问,这时应禁止后续读进程的请求,等待到已在共享文件的读进程执行完毕则立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次运行。为此,增加一个信号量并且在上面的程序中 writer()和reader()函数中各增加一对PV操作,就可以得到写进程优先的解决程序。

  1. int count = 0; //用于记录当前的读者数量
  2. semaphore mutex = 1; //用于保护更新count变量时的互斥
  3. semaphore rw=1; //用于保证读者和写者互斥地访问文件
  4. semaphore w=1; //用于实现“写优先”
  5. writer(){
  6. while(1){
  7. P(w); //在无写进程请求时进入
  8. P(rw); //互斥访问共享文件
  9. writing; //写入
  10. V(rw); // 释放共享文件
  11. V(w) ; //恢复对共享支件的访问
  12. }
  13. }
  14. reader () { //读者进程
  15. while (1){
  16. P (w) ; // 在无写进程请求时进入
  17. P (mutex); // 互斥访问count变量
  18. if (count==0) //当第一个读进程读共享文件时
  19. P(rw); //阻止写进程写
  20. count++; //读者计数器加1
  21. V (mutex) ; //释放互斥变量count
  22. V(w); //恢复对共享文件的访问
  23. reading; //读取
  24. P (mutex) ; //互斥访问count变量
  25. count--; //读者计数器减1
  26. if (count==0) //当最后一个读进程读完共享文件
  27. V(rw); //允许写进程写
  28. V (mutex); //释放互斥变量count
  29. }
  30. }

3、死锁

 死锁就是所有线程都在相互等待释放资源,导致谁也无法继续执行下去。比如哲学家进餐问题,当每个人都同时拿起左边的筷子,那么同时每个人都在等待有人放下获取另一支筷子,这时就构成了死锁。可见竞争资源以及推进进程顺序不当会引发死锁。下面一些简单的规则可以帮助我们避免死锁:

1. 如果有多个锁的话,尽量确保每个线程都是按相同的顺序加锁,按加锁相反的顺序解锁。

(即加锁a->b->c,解锁c->b->a)

2. 防止发生饥饿。即设置一个超时时间,防止一直等待下去。

3. 不要重复请求同一个锁。

4. 设计应力求简单。加锁的方案越复杂就越容易出现死锁。

 对于解决哲学家进餐问题有两种解决办法:

(1)同时只允许一位哲学家就餐
  1. semaphore fork[5]={1,1,1,1,1};
  2. semaphore mutex = 1;
  3. void philosopher(int i){
  4. while(TRUE){
  5. think();
  6. P(mutex)
  7. P(fork[i]);
  8. P(fork[(i+1)%N);
  9. V(mutex)
  10. eat();
  11. V(fork[i]);
  12. V(fork[(i+1)%N];
  13. }
(2)对哲学家顺序编号,要求奇数号哲学家先抓左边的叉子,然后再抓他右边的叉子,而偶数号哲学家刚好相反。
  1. semaphore fork[5]={1,1,1,1,1};
  2. void philosopher(int i){
  3. while(TRUE){
  4. think();
  5. if(i%2==1){
  6. P(fork[i]);
  7. P(fork[(i+1)%N);
  8. }else{
  9. P(fork[(i+1)%N]);
  10. P(fork[i]);
  11. }
  12. eat();
  13. V(fork[i]);
  14. V(fork[(i+1)%N];
  15. }

2017-2018-1 20179215《Linux内核原理与分析》第六周作业的更多相关文章

  1. 2017-2018-1 20179215《Linux内核原理与分析》第二周作业

    20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...

  2. 20169212《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 这一周学习了MOOCLinux内核分析的第一讲,计算机是如何工作的?由于本科对相关知识的不熟悉,所以感觉有的知识理解起来了有一定的难度,不过多查查资 ...

  3. 20169210《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 本周作业分为两部分:第一部分为观看学习视频并完成实验楼实验一:第二部分为看<Linux内核设计与实现>1.2.18章并安装配置内核. 第 ...

  4. 2018-2019-1 20189221 《Linux内核原理与分析》第九周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第九周作业 实验八 理理解进程调度时机跟踪分析进程调度与进程切换的过程 进程调度 进度调度时机: 1.中断处理过程(包 ...

  5. 2019-2020-1 20199329《Linux内核原理与分析》第九周作业

    <Linux内核原理与分析>第九周作业 一.本周内容概述: 阐释linux操作系统的整体构架 理解linux系统的一般执行过程和进程调度的时机 理解linux系统的中断和进程上下文切换 二 ...

  6. 2019-2020-1 20199329《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 一.上周问题总结: 未能及时整理笔记 Linux还需要多用 markdown格式不熟练 发布博客时间超过规定期限 二.本周学习内容: <庖丁解 ...

  7. 2019-2020-1 20209313《Linux内核原理与分析》第二周作业

    2019-2020-1 20209313<Linux内核原理与分析>第二周作业 零.总结 阐明自己对"计算机是如何工作的"理解. 一.myod 步骤 复习c文件处理内容 ...

  8. 2018-2019-1 20189221《Linux内核原理与分析》第一周作业

    Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...

  9. 《Linux内核原理与分析》第一周作业 20189210

    实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...

  10. 2018-2019-1 20189221《Linux内核原理与分析》第二周作业

    读书报告 <庖丁解牛Linux内核分析> 第 1 章 计算工作原理 1.1 存储程序计算机工作模型 1.2 x86-32汇编基础 1.3汇编一个简单的C语言程序并分析其汇编指令执行过程 因 ...

随机推荐

  1. JSP具体篇——application

    application对象 application对象用于保存全部应用程序中的共同拥有数据.它在server启动时自己主动创建.在server停止时自己主动销毁. 当application对象没有被销 ...

  2. Jmeter 04 JMeter 负载与监听

    1. 场景设计 2. 场景设置 3. JMeter性能参数配置 4. 测试监听

  3. Java和js的区别,以及Java和c的区别

    刚开始的时候我们也搞不清这些概念,不过后来就慢慢清晰了,首先和大家谈谈Java和js的区别,最简单的区别就是一个是后端,一个是前端.   java是纯面向对象语言,javascrip其实和Java是完 ...

  4. PBR探索

    原理 根据能量守恒,以及一系列光照原理得出微表面BRDF(Bidirectional Reflectance Distribution Function)公式 // D(h) F(v,h) G(l,v ...

  5. zip filter map 列表生成器

    map map(function, list): 就是对list 中的每一个元素都调用function函数进行处理,返回一个map的对象 list一下就可以生成一个列表 或者for循环该对象就可以输出 ...

  6. iOS 流媒体 基本使用 和方法注意

    项目里面需要添加视频方法   我自定义 选用的是 avplayer  没选择 MediaPlayer  原因很简单 , avplayer 会更容易扩展  有篇博客 也很好地说明了 使用avplayer ...

  7. Linux平台下贪吃蛇游戏的运行

    1.参考资料说明: 这是一个在Linux系统下实现的简单的贪吃蛇游戏,同学找帮忙,我就直接在Red Hat中调试了一下,参考的是百度文库中"maosuhan"仁兄的文章,结合自己的 ...

  8. 【leetcode刷题笔记】Combination Sum II

    Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in ...

  9. zookeeper 实战案例分享:cruator客户端编程

    上两篇介绍了zookeeper服务器端的安装和配置,今天分享下利用zookeeper客户端编程来实现配置文件的统一管理,包括文件添加.删除.更新的同步. 比如,连接数据库信息的配置文件,一般每个应用服 ...

  10. android电池(五):电池 充电IC(PM2301)驱动分析篇【转】

    本文转载自:http://blog.csdn.net/xubin341719/article/details/8970363 android充电这块,有的电源管理芯片内部包含充电管理,如s5pv210 ...