网易云课堂学习
把write系统调用加入到MenuOS里面
我在试验过程中在MenuOS里加入了time、time-asm、write和write-asm命令。以time和time-asm为例,
步骤如下
- 更新menu代码到最新版
- 在main函数中增加MenuConfig
- 增加对应的Time函数和TimeAsm函数
- make rootfs
实验结果如图所示
然后使用gdb跟踪分析write系统调用函数。write对应的系统调用函数是sys_write
分析system_call的执行过程
首先看系统调用的初始化,在start_kernel里面有trap_init,也就是中断初始化的一个函数,trap_init里面有set_system_trap_gate(SYSCALL_VECTOR,&system_call)&system_call就是系统调用的入口。代码中一旦出现init 0x80的指令,立即就会跳转到system_call的位置,即ENTRY(system_call).
看一下ENTRY(system_call)的代码
490ENTRY(system_call)
491 RING0_INT_FRAME # can't unwind into user space anyway
492 ASM_CLAC
493 pushl_cfi %eax # save orig_eax
494 SAVE_ALL
495 GET_THREAD_INFO(%ebp)
496 # system call tracing in operation / emulation
497 testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
498 jnz syscall_trace_entry
499 cmpl $(NR_syscalls), %eax
500 jae syscall_badsys
501syscall_call:
502 call *sys_call_table(,%eax,4)
503syscall_after_call:
504 movl %eax,PT_EAX(%esp) # store the return value
505syscall_exit:
506 LOCKDEP_SYS_EXIT
507 DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
508 # setting need_resched or sigpending
509 # between sampling and the iret
510 TRACE_IRQS_OFF
511 movl TI_flags(%ebp), %ecx
512 testl $_TIF_ALLWORK_MASK, %ecx # current->work
513 jne syscall_exit_work
514
515restore_all:
516 TRACE_IRQS_IRET
关于汇编跳转指令的说明
JNS表示如果符号位没有被置位则跳转。
JAE表示如果超过或等于(>=)则跳转。
JNE表示如果不相等(<>)则跳转。
先判断是否符合跳转条件,再执行后面相应指令。
system_call的中断处理过程如下:
- 1) SAVE_ALL保护现场。保护的是发生中断处,进程下一条指令的地址,还包括标志寄存器的内容。
- 2)通过call *sys_call_table(,%eax,4)调用系统调用表,传递系统调用号,调用相应函数。
- 3)syscall_after_call: movl %eax,PT_EAX(%esp)保存返回值。
- 4)检测是否处理syscall_exit_work,如果不处理的话恢复现场。
- 5)返回用户态。
Linux内核设计与实现
第9章内核同步介绍
(1)多个执行线程同时访问和操作数据,就有可能发生各线程之间相互覆盖共享数据的情况,造成共享数据处于不一致状态。并发访问共享数据是造成系统不稳定的一类隐患,而且这种错误一般难以跟踪和调试。在使用共享内存的应用程序中,程序员必须特别留意保护共享资源,防止共享资源并发访问,内核也不例外。
(2)临界区是访问和操作共享数据的代码段。
(3)避免并发和防止竞争条件称为同步。
(4)原子操作在操作执行结束前不可被打断。
(5)可以通过加锁来保护临界区资源。
- 内核中可能造成并发的原因:
- 中断——中断几乎可以在任何时刻异步发生,也就可以随时打断当前正在执行的代码。
- 软中断和tasklet——内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在执行的代码。
- 内核抢占——因为内核具有抢占性,所以内核中的任务可能会被另一任务抢占。
(6)尽管释放锁的顺序和死锁无关,但最好还是以获得锁的相反顺序来释放锁。
第10章内核同步方法
原子操作:
内核提供了两组原子操作接口,一组针对整数进行操作,另一组针对单独的位进行操作。
针对整数的原子操作只能对atomic_t类型的数据进行处理。
原子性与顺序性:原子性确保指令执行期间不被打断,要么全部执行,要么根本不执行。顺序性确保即使两条或多条指令同时出现在独立的执行线程中,他们依然保持本该的执行顺序。顺序性通过屏障指令来实施。
自旋锁:
自旋锁只能被一个可执行线程持有。如果一个线程试图争用自旋锁,他就会处于忙循环——旋转——等待锁重新可用中一直自旋,浪费处理器时间。
所以自旋锁不应被长时间占有。这正是使用自旋锁的初衷:在短期内进行轻量级加锁。
处理锁争用的其他方式:让请求线程睡眠,直到锁重新可用时唤醒他。
中断处理程序中使用自旋锁时要在获取锁之前禁止本地中断,否则中断处理程序会打断正持有锁的内核代码。
下半部可以抢占进程上下文中的代码,所以下半部和进程上下文共享数据时,要保护进程上下文中的共享数据。
信号量
信号量是一种睡眠锁,他比自旋锁提供了更好的处理器利用率,因为没有把时间花费在忙等待上。
在占用信号量的同时不能占用自旋锁。
锁被持有时间长时用信号量,被持有时间短时用自旋锁。
信号量包括互斥信号量和计数信号量。计数信号量在一个时刻至多有count个持有者。
互斥体是相对于信号量更简单的睡眠锁。
自旋锁与信号量的比较
需求 | 建议的加锁方法 |
---|---|
低开销加锁 | 优先使用自旋锁 |
短期锁定 | 优先使用自旋锁 |
长期加锁 | 优先使用互斥体 |
中断上下文中加锁 | 使用自旋锁 |
持有锁需要睡眠 | 使用互斥体 |
自旋锁方法列表
方法 | 描述 |
---|---|
spin_lock() | 获取指定的自旋锁 |
spin_lock_irq() | 禁止本地中断并获得指定的锁 |
spin_lock_irqsave() | 保存本地中断的当前状态,禁止本地中断,并获取指定的锁 |
spin_unlock() | 释放指定的锁 |
spin_unlock_irq() | 释放指定的锁,并激活本地中断 |
spin_unlock_irqrestore() | 释放指定的锁,并让本地中断恢复到以前状态 |
spin_lock_init() | 动态初始化指定的spinlock_t |
spin_trylock() | 试图获取指定的锁,如果未获取,则返回非0 |
spin_is_locked() | 如果指定的锁当前正在被获取,则返回非0,否则返回0 |
信号量方法列表
方法 | 描述 |
---|---|
sema_init(struct semaphore *,int) | 以指定的计数值初始化动态创建的信号量 |
init_MUTEX(struct semaphore *) | 以计数值1初始化动态创建的信号量 |
init_MUTEX_LOCKED(struct semaphore *) | 以计数值0初始化动态创建的信号量 |
down_interruptible(struct semaphore *) | 以试图获得指定的信号量,如果信号量已被争用,则进入可中断睡眠状态 |
down (struct semaphore *) | 以试图获得指定的信号量,如果信号量已被争用,则进入不可中断睡眠状态 |
down_trylock (struct semaphore *) | 以试图获得指定的信号量,如果信号量已被争用,则立刻返回非0值 |
up (struct semaphore *) | 以释放指定的信号量,如果睡眠队列不空,则唤醒其中一个任务 |
出现的问题
(1)实验楼的实验环境不稳定,总是出现错误
(2)加入自己写的系统调用的时候,在write函数后面没写(int argc,char *argv[])这句,出现错误,经查询了解到这两个就是用于接受参数和记录参数信息的。