视频知识学习
给MenuOS增加time和time-asm命令
1.更新menu代码到最新版
2.再main()函数中增加MenuConfig
3.增加对应的Time函数和TimeAsm函数(这里的函数要换成我们自己编写的使用系统调用的函数,比如mkdir和mkdirAsm)
4.make rootfs (帮我们自动编译自动生成根文件系统,自动帮我们启动起来menuos)
使用gdb跟踪系统调用内核函数sys_time
删除旧的的menu目录,重新下载新的版本。操作如下:
$ cd /home/shiyanlou/LinuxKernel
$ rm -rf menu
$ git clone http://github.com/mengning/menu.git
$ cd menu
修改menu目录下的test.c文件,把上节课自己写的系统调用代码加进去,做成两个菜单命令。test.c文件新增代码如下:
#include <sys/stat.h>
#include <sys/types.h>
...
int MakeDir() {
int ret = 0;
ret = mkdir("./testdir", 0777);
printf("ret is: %d.
", ret);
return 0;
}
int MakeDirAsm() {
int ret = 0;
//ret = mkdir("./testdir", 0777);
char *dir = "./testdir";
int mode = 0777;
asm volatile(
"movl %1, %%ebx
"
"movl %2, %%ecx
"
"movl $39, %%eax
"
"int $0x80
"
"movl %%eax, %0
"
: "=m"(ret)
: "m"(dir), "m"(mode)
);
printf("ret is: %d.
", ret);
return 0;
}
int main()
{
...
MenuConfig("make-dir","Make Directory",MakeDir);
MenuConfig("make-dir-asm","Make Directory(asm)",MakeDirAsm);
...
}
重新编译并启动程序:
$ make rootfs
在界面中输入help命令,可以看到make-dir和make-dir-asm这两个命令已经成功地加到这个系统里。执行make-dir命令也可以正常返回结果0,表示命令执行成功。如下图所示:
跟踪mkdir对应的系统调用内核函数
使用qumu命令重新启动内核并使用-s和-S参数“冻结”系统执行,命令如下:
$ cd /home/shiyanlou/LinuxKernel
$ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
程序“冻结”的效果如下,启动窗口中提示程序“[stopped]”
此时为了使用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 ?? ()
(gdb) continue
其中,file命令用于加载linux内核代码的符号表,target用于将gdb调试工具连接到已经启动的程序上。完成之后,执行continue继续程序的执行。
待系统启动完成后,按 < C + c > 中断系统后给程序打上断点,然后执行continue继续运行程序,如下:
(gdb) break sys_mkdir
Breakpoint 1 at 0xc1139220: file fs/namei.c, line 3525.
(gdb) continue
设置一个sys_time
断点,启动menuOS
,执行time命令,进入系统调用,会停在在sys_time
这个函数。
系统调用过程
系统调用是如何工作的呢?在上面的例子中,它是怎么能够调用到sys_mkdir内核函数的呢?
当程序执行到 int 0x80 这个指令时就会调转到system_call处执行,这个对应关系是由中断向量设计的时候就固定的。
time_asm
发现还是停在了sys_time
的位置,在sys_call
并不能停下。
第9,10章课程学习
加锁——可以防止并发执行,并且保护队列不受竞争条件的影响,任何要访问队列的代码首先都需要占住相应的锁,这样该锁就能阻止来自其他执行线程的并发访问。
内核中可能造成并发执行的原因,如下:
中断——中断几乎可以在任何时候异步发生,也就可能随时打断当前正在执行的代码。
软中断和tasklet——内核能在任何时候唤醒或调度软中断和tasklet,打断当前正在执行的代码。
内核抢占——因为内核具有抢占性,所以内核中的任务可能会被另一任务抢占。
睡眠及与用户空间的同步——在内核执行的进程可能会睡眠,这就会唤醒调度程序,从而导致调度一个新的用户进程执行。
对称多处理——两个或多个处理器可以同时执行代码。
死锁的产生需要一定条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用了。所有线程都在相互等待,但它们永远不会释放已经占有的资源。于是任何线程都无法继续,这便意味着死锁的发生。
无论在编写哪种内核代码,首先应该考虑的就是保护数据不被并发访问,要加锁。
原子操作——可以保证指令以原子的方式执行—执行过程不被打断。内核提供了两组原子操作接口—一组针对整数进行操作,另一组针对单独的位进行操作。
自旋锁——最多只能被一个可执行线程持有。在任意时间,自旋锁都可以防止多于一个的执行线程同时进入临界区。大原则:针对代码加锁会使得程序难以理解,并且容易引发竞争条件,正确的做法应该是对数据而不是代码加锁。
读-写自旋锁——读/写代码锁有时叫做共享/排斥锁,写操作要求完全互斥,只要没有写操作,多个并发的读操作都是安全的。
信号量——如果加锁时间不长,并且代码不会睡眠,利用自旋锁是最佳选择。如果加锁时间可能很长或者代码在持有锁时有可能睡眠,那么最好使用信号量来完成加锁功能。
读-写信号量——都是互斥信号量。除非读和写可以明白无误的分割开来,否则最好不使用它。
互斥体——任何可以睡眠的强制互斥锁,互斥体是一种互斥信号。mutex的使用计数永远是1.
完成变量——如果在内核中一个任务需要发出信号通知另一个任务发生了某个特定事件,利用完成变量是使两个任务得以同步的简单方法。
BLK:大内核锁——BKL是一个全局自旋锁,使用它主要是为了方便实现从Linux最初的SMP过渡到细粒度加锁机制。BKL更像是保护代码而不是保护数据。
顺序锁——顺序锁用于读写共享数据,依靠的是序列计数器,seq对写者更有利。
禁止抢占——内核抢占代码使用自旋锁作为非抢占区域的标记,如果一个自旋锁被持有,内核便不能进行抢占。
顺序和屏障——当处理多处理器之间或硬件设备之间的同步问题时,有时需要在你的程序代码中以指定的顺序发出读内存和写内存的指令,这些确保顺序的指令称作屏障。