20169212《Linux内核原理与分析》第七周作业

实验

给MenuOS增加time和time-asm命令的方法:

  1. 更新menu代码到最新版
  2. 再main()函数中增加MenuConfig
  3. 增加对应的Time函数和TimeAsm函数(这里的函数要换成我们自己编写的使用系统调用的函数,比如mkdir和mkdirAsm)
  4. make rootfs (帮我们自动编译自动生成根文件系统,自动帮我们启动起来menuos)

接下来我要使用gdb跟踪分析一个系统调用内核函数(mkdir)

这次我实验所用的系统调用仍然是是mkdir

首先,我们需要把上周做的两个实验加入到我们的MenuOS中,变成MenuOS中的两个命令。如图:
20169212《Linux内核原理与分析》第七周作业_第1张图片

方法是要将函数加入到test.c中(因为实验楼中的clone无法使用),加入命令为:

int Mkdir()
{
        int flag;
        flag = mkdir("/home/shiyanlou/testdir");
        if (flag == -1)
                printf("mkdir failed!\n");
        else
                printf("make dirctory success!\n");
        return 0;
}

int MkdirAsm()
{
   int flag;
   char *dir = "/home/shiyanlou/testdir2";
   asm volatile(
       "movl $0x27,%%eax\n\t"
       "movl %1,%%ebx\n\t"
       "int $0x80\n\t"
       "movl %%eax,%0\n\t"
       :"=m"(flag)
       :"c"(dir)
       );
   if(flag==0)
      printf("mkdir success!\n");
   else
      printf("mkdir failed!\n");
   return 0;
}

在main函数中加入:

    MenuConfig("mkdir","Make A Directory",Mkdir);
    MenuConfig("mkdir-asm","Make A Directory(asm)",MkdirAsm);

make rootfs就可以自动生成,输入help可以看到mkdir,如下结果:

20169212《Linux内核原理与分析》第七周作业_第2张图片

下面开始用gdb调试:

cd LinuxKernel
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
水平分割
gdb
file linux-3.18.6/vmlinux   //加载内核
target remote:1234          //链接到menu os里
b start_kernel              //在start_kernel处设置断点
c                           //继续执行,到start_kernel 停下来

20169212《Linux内核原理与分析》第七周作业_第3张图片

list                        //查看startkernel这段代码
b sys_mkdir                 //在我们要分析的这个系统调用处设置断点
c                           //继续执行
c
c

20169212《Linux内核原理与分析》第七周作业_第4张图片

启动menuos,输入mkdir,看gdb调试结果
20169212《Linux内核原理与分析》第七周作业_第5张图片

如果我们换用视频里的sys_time设置断点进行调试,之前的方法与mkdir类似,然后可以继续跟踪调试如下:

b sys_time
c
c
c
list
s                           //单步执行
s                           //单步执行
finish                      //函数执行完
s
s
n
n

20169212《Linux内核原理与分析》第七周作业_第6张图片

这边代码就不大好调试了,如果我们再设置一个断点,当我们用int 0x80时,cpu自动跳转到system_call这个函数,下面我们直接设置断点system_call,是否能停下来呢?

b system_call
c

发现time返回了。在menuos中输入time—asm,停在system_time的位置,system_call并不能停下。老师说这里的system_call他不是一个正常的函数,它是一段特殊的汇编代码,可能gdb对他不支持,我们只能调试系统调用函数和对应的内核函数,调试内核函数的处理过程,但不能跟踪entry_32.s这个汇编代码,在system_call处并没有停下来。system_call在entry_32.s中,可以找到ENTRY(system_call),gdb可以发现一个函数原型,实际上不是函数,是汇编代码的起点,gdb现在还不能跟踪它。

关于从system_call到iret之间的过程

画了一个大致的流程图,如下:
20169212《Linux内核原理与分析》第七周作业_第7张图片
首先保护现场(SAVE_ALL:保存需要用到的寄存器数据);退出恢复现场(RESTORE_ALL:退出中断程序,恢复保存寄存器的数据)。
下面为SAVE_ALL的实现:

.macro SAVE_ALL
    cld
    PUSH_GS
    pushl_cfi %fs
    /*CFI_REL_OFFSET fs, 0;*/
    pushl_cfi %es
    /*CFI_REL_OFFSET es, 0;*/
    pushl_cfi %ds
    /*CFI_REL_OFFSET ds, 0;*/
    pushl_cfi %eax
    CFI_REL_OFFSET eax, 0
    pushl_cfi %ebp
    CFI_REL_OFFSET ebp, 0
    pushl_cfi %edi
    CFI_REL_OFFSET edi, 0
    pushl_cfi %esi
    CFI_REL_OFFSET esi, 0
    pushl_cfi %edx
    CFI_REL_OFFSET edx, 0
    pushl_cfi %ecx
    CFI_REL_OFFSET ecx, 0
    pushl_cfi %ebx
    CFI_REL_OFFSET ebx, 0
    movl $(__USER_DS), %edx
    movl %edx, %ds
    movl %edx, %es
    movl $(__KERNEL_PERCPU), %edx
    movl %edx, %fs
    SET_KERNEL_GS %edx
.endm

在这段代码中,保存了相关寄存器的值。 他们依次是:ES,DS,EAX,EBP,EDI,ESI,EDX,ECX,EBX等等。从这里寄存器的顺序可以知道压栈的最后压入的是ebx,这里压入的栈是内核栈。

遇到的问题

  1. 在使用实验楼clone的时候出现问题,后将test.c文件更改进行解决
  2. 实验楼一直很卡,试验了好多次最后在MenuOs上输入的mkdir命令上还是卡住了

书上的内容整理

  1. 内核同步介绍:留意保护共享资源,防止共享资源并发访问,发生各个线程之间相互覆盖共享数据的情况,造成访问数据不一致;临界区和竞争条件,临界区:访问和操作共享数据的代码段。 竞争条件:两个执行线程处于同一个临界区中;内核各个部分都会调用两个函数,一个函数将新请求添加到队列尾部,另一个函数从队列头删除请求,然后处理它。
  2. 锁的使用是自愿的、非强制的,它完全属于一种编程者自选的编程手段;内核中造成并发的原因:(1)中断:任何时刻异步发生,打断当前执行的代码(2)软中断和tasklet:任何时刻唤醒或调度软中断、tasklet(3)内核抢占(preempt)(4)睡眠及与用户空间的同步:唤醒调度程序,调度新进程执行(5)对称多处理器(SMP);编程需注意的问题: (1)数据是否全局?除了当前线程,其他线程是否可以访问? (2)数据是否在进程/中断上下文中共享?是否在两个不同中断中共享?(3)进程在访问数据时可否被抢占?被调度的新进程是否会访问同一数据? (4)当前进程是否会睡眠(阻塞)在某些资源上?共享数据处于何种状态? (5)怎样防止数据失控?;加锁:按顺序加锁。使用嵌套锁必须以相同顺序获取锁;防止发生饥饿; 不要重复请求同一个锁; 设计力求简单;建议以获取锁相反的顺序来释放锁。
  3. 原子操作:原子操作执行过程不被打断,原子操作接口分为整数操作接口和单独位操作接口。
  4. 自旋锁:自旋锁最多只能被一个可执行线程持有。若一个线程试图获得一个被征用的自旋锁,线程会一直忙循环、选择、等待锁可用。缺点:由于自旋锁在等待时自旋(浪费处理器时间),因此自旋锁不应长时间持有。优点:线程不用睡眠,不用进行上下文切换。自旋锁可以使用在中断处理程序中。对于软中断,无论是否同种类型,如果数据被软中断共享,那么它必须得到锁的保护。读写自旋锁:读写不同锁,多人和可并发持有读者锁,写锁只能被一个任务持有。
  5. 信号量特点: Linux中的信号量是一种睡眠锁; 信号量适用于锁会被长期占有的情况;
    由于信号量会睡眠,所以只能用于进程上下文中,中断上下文不支持调度;可以在持有信号量时睡眠; 不可以在持有信号量时使用自旋锁; 信号量允许任意数目的锁持有者,自旋锁一个时刻只允许一个任务持有;在声明时课指定信号量拥有的持有者数量。
  6. 禁止抢占:内核代码使用自旋锁作为非抢占区域的标记。preempt_disable() 增加抢占计数值,从而禁止内核抢占;preempt_enable() 减少抢占计数值,当减为0时检查和执行被挂起的任务; preempt_count() 返回抢占计数。

你可能感兴趣的:(20169212《Linux内核原理与分析》第七周作业)