20169219《linux内核原理与分析》第七周作业

网易云课堂学习

把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[])这句,出现错误,经查询了解到这两个就是用于接受参数和记录参数信息的。

posted @ 2016-11-06 17:39  毛卫华  阅读(188)  评论(0编辑  收藏  举报