代码改变世界

2017-2018-1 20179226《Linux内核原理与分析》第六周作业

2017-11-05 10:56  20179226任逸飞  阅读(259)  评论(0编辑  收藏  举报

实验

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

1.更新menu代码到最新版
2.在main()函数中增加MenuConfig
3.增加对应的Time函数和TimeAsm函数
4.make rootfs
实验结果如图所示

我上周使用的系统调用是creat,加到test.c的函数的命令为

int create(int argc, char *argv[])
{
    int ret =0;
    char* filename = "createfile1";
    mode_t mode = 0755;
    ret = creat(filename,mode);
    printf("file %d create success\n",ret);
    return 0;
}
int createasm(int argc, char *argv[])
{
int ret =0;
char* filename = "createfile2";
mode_t mode = 0755;
asm volatile(
        "movl $8,%%eax\n\t"
        "int $0x80\n\t"
        "movl %%eax,%0\n\t"
        :"=m"(ret)
        :"b"(filename),"c"(mode)
        );
printf("file %d create success\n",ret);
return 0;
}

在main函数中增加的MenuConfig命令为

MenuConfig("Create","Create a file",create);
MenuConfig("Create-asm","Create a file(asm)",createasm);

使用qumu命令重新启动内核并使用-s和-S参数“冻结”系统执行,命令如下:

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

打开gdb调试,指令如下:

$ gdb
(gdb) file linux-3.18.6/vmlinux
Reading symbols from linux-3.18.6/vmlinux...done.
(gdb) target remote:1234
Remote debugging using :1234
0x0000fff0 in ?? ()

在sys_creat处设置断点,然后按c执行,在系统界面中输入新增的Create命令,中断被执行

creat系统调用在open.c文件第1034行,它实际上是继续调用了sys_open函数进行处理。

分析system_call到iret之间的过程

SAVE_ALL保存的一帧现场所有的寄存器,与该过程所要传递的pt_regs结构中的成员一致。pt_regs描述了在执行系统调用时,用户态下的CPU寄存器在核心态的栈中的保存情况。
下面为SAVE_ALL的实现:

#define 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

遇到的问题

在test.c中加入上周写的函数系统调用的时候,总是出现错误,最后上网查询发现在creat函数后面应该加上(int argc,char *argv[]),运行果然成功。经过查找资料得知这两个就是用于接受参数和记录参数信息的。

阅读教材第9、10章

1.临界区和竞争条件

所谓临界区就是访问和操作共享数据的代码段。如果两个执行线程有可能处于同一个临界区中同时执行,那么这就是程序包含的一个bug。如果这种情况确实发生了,我们就称它是竞争条件。避免并发和防止竞争条件称为同步。

2.加锁

锁提供的就是这种机制:可以确保一次有且仅有一个线程对数据结构进行操作,或者当另一个线程对临界区进行标记时,就禁止(或者说锁定)其他访问。

3.内核中造成并发执行的原因

1)中断——中断几乎可以在任何时刻异步发生,也可能随时打断当前正在执行的代码。
2)软中断和tasklet——内核嫩黄瓜在任何时刻唤醒或调度软中断和tasklet,打断当前正在执行的代码。
3)内核抢占——因为内核具有抢占性,所以内核中的任务可能会被另一任务抢占。
4)睡眠及与用户空间的同步——在内核执行的进程可能会睡眠。这就会唤醒调度程序,从而导致调度一个新的用户进程执行。
5)对称多处理——两个或多个处理器可以同时执行代码。

4.死锁

死锁的产生需要一定条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源已经被占用了。所有的线程都在相互等待,但他们永远不会释放已经占有的资源。于是任何线程都无法继续,这便意味着死锁的发生。

5.避免死锁的规则

1)按顺序加锁 2)防止发生饥饿 3)不要重复请求同一个锁 4)设计应力求简单

6.争用和扩展性

锁的争用是指当锁正在被占用时,有其他线程试图获得该锁。扩展性是对系统可扩展程度的一个量度。扩展性是对系统可扩展程度的一个量度。

7.原子操作

原子操作可以保证指令以原子的方式执行—执行过程不被打断。内核提供了两组原子操作接口—一组针对整数进行操作,另一组针对单独的位进行操作。针对整数的原子操作只能对atomic_t类型的数据进行处理。主要出于两个原因:首先,让原子函数只接收atomic_t类型的操作数,可以确保原子操作只与这种特殊类型数据一起使用。同时,这也保证了该类型的数据不会被传递给任何非原子函数。其次,使用atomic_t类型确保编译器不对相应的值进行访问优化。

8.自旋锁

Linux内核中最常见的锁是自旋锁。自旋锁最多只能被一个可执行线程持有。在任意时间,自旋锁都可以防止多于一个的执行线程同时进入临界区。大原则:针对代码加锁会使得程序难以理解,并且容易引发竞争条件,正确的做法应该是对数据而不是代码加锁。自旋锁的基本使用形式如下:

DEFINE_SPINLOCK(mr_lock);
spin_lock(&mr_lock);
/*临界区...*/
spin_unlock(&mr_lock);

9.读-写自旋锁

读/写代码锁有时叫做共享/排斥锁,写操作要求完全互斥,只要没有写操作,多个并发的读操作都是安全的。

10.信号量

Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个不可用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。如果加锁时间不长,并且代码不会睡眠,利用自旋锁是最佳选择。如果加锁时间可能很长或者代码在持有锁时有可能睡眠,那么最好使用信号量来完成加锁功能。

11.读-写信号量

读-写信号量都是互斥信号量。除非读和写可以明白无误的分割开来,否则最好不使用它。

12.互斥体

互斥体是任何可以睡眠的强制互斥锁,互斥体是一种互斥信号。mutex的使用计数永远是1。

13.完成变量

如果在内核中一个任务需要发出信号通知另一个任务发生了某个特定事件,利用完成变量是使两个任务得以同步的简单方法。

14.BLK:大内核锁

BKL是一个全局自旋锁,使用它主要是为了方便实现从Linux最初的SMP过渡到细粒度加锁机制。其特性为1)持有BKL的任务仍然可以睡眠。2)BKL是一种递归锁。3)BKL只可以用在进程上下文中。4)新的用户不允许使用BKL。

15.顺序锁

顺序锁简称seq锁,用于读写共享数据,依靠的是序列计数器,seq锁在你遇到如下需求时将是最理想的选择:1)你的数据存在很多读者。2)你的数据写者很少。3)你希望写优先于读,而且不允许读者让写者饥饿。4)你的数据很简单。

16.禁止抢占

内核抢占代码使用自旋锁作为非抢占区域的标记,如果一个自旋锁被持有,内核便不能进行抢占。

17.顺序和屏障

当处理多处理器之间或硬件设备之间的同步问题时,有时需要在你的程序代码中以指定的顺序发出读内存和写内存的指令,这些确保顺序的指令称作屏障。