2019-2020-1 20199309《Linux内核原理与分析》第六周作业

一、系统调用实验(下):

1.编辑 menu 中的 text.c 文件,给MenuOS增加 rename 和 rename_asm命令:

make rootf 打开 menu 镜像,可以看到MenuOS菜单中新增了2条命令:

2.gdb 跟踪 sys_rename:
同第二个实验相同,先使得 CPU 静止,在 sys_rename 处设置断点,在MenuOS中执行rename命令,发现停在SyS_rename(定义在fs/namei.c中)处,用宏来实现。然后继续单步执行:

大家知道执行int 0x80,CPU就会自动跳转到 sys_call 来执行。所以为了跟踪 sys_call,在 sys_call 处设置断点,在MenuOS中执行 rename_asm 命令,依旧停在 SyS_rename 处,并没有停在所期待的 sys_call 处,这是因为 system_call 不是正常的函数,是一段特殊的汇编代码,gdb还不能进行跟踪。

3.分析系统调用的处理过程:

系统调用机制的初始化是在 start_kernel 中的 trap_init()(定义在/arch/x86/kernel/traps.c中)里进行的:

#ifdef CONFIG_X86_32
   set_system_trap_gate(SYSCALL_VECTOR, &system_call); //SYSCALL_VECTOR系统调用的中断向量,&system_call是 system_call的入口,一旦执行int 0x80,CPU就会立即跳转到此处
   set_bit(SYSCALL_VECTOR, used_vectors);
#endif

在 /arch/x86/include/asm/irq_vectors.h 中查看 SYSCALL_VECTOR 的值,确实是0x80:

#ifdef CONFIG_X86_32
# define SYSCALL_VECTOR         0x80
#endif

system_call 的相关代码的位置是 /arch/x86/kernel/entry_32 :

ENTRY(system_call)
     RING0_INT_FRAME    
     ASM_CLAC        
     pushl_cfi %eax            //保存系统调用号;
     SAVE_ALL                  //可以用到的所有CPU寄存器保存到栈中
     GET_THREAD_INFO(%ebp)     //ebp用于存放当前进程thread_info结构的地址
     testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
     jnz syscall_trace_entry
     cmpl $(nr_syscalls), %eax  //检查系统调用号(系统调用号应小于NR_syscalls),
     jae syscall_badsys         //不合法,跳入到异常处理
 syscall_call:
     call *sys_call_table(,%eax,4) //合法,对照系统调用号在系统调用表中寻找相应服务例程
     movl %eax,PT_EAX(%esp)        //保存返回值到栈中
 syscall_exit:  
     testl $_TIF_ALLWORK_MASK, %ecx   //检查是否需要处理信号
     jne syscall_exit_work        //需要,进入 syscall_exit_work
 restore_all: 
     TRACE_IRQS_IRET              //不需要,执行restore_all恢复,返回用户态
 irq_return:
     INTERRUPT_RETURN             //相当于iret

注:

(1)sys_call_table(,%eax,4)的理解:因为分派表中的每个表项占4个字节,所以先把系统调用号(eax)乘以4,再加上 sys_call_table 分派表的起始地址,即是所调用的服务例程。

(2)GET_THREAD_INFO 宏用于获得当前进程的thread_info结构的地址,即获取当前进程的信息。

(3)syscall_exit_work 执行了一些进程调度、消息传递的工作,如果进行了进程切换,可能要继续触发新的中断,执行新的系统调用。

二、课本笔记:

多个执行线程同时访问和操作数据,就有可能发生各线程之间相互覆盖共享数据的情况,造成共享数据处于不一致状态。临界区是访问和操作共享数据的代码段。避免并发和防止竞争条件称为同步。并发产生的原因有中断、软中断和tasklet、内核抢占、睡眠及与用户空间的同步、对称多处理。

  可以通过加锁来保护临界区资源。要给数据而不是给代码加锁。最开始设计代码的时候就要考虑加入锁。按顺序加锁、防止发生饥饿、不要重复请求同一个锁、设计力求简单对避免死锁大有帮助。加锁粒度用来描述加锁保护的数据规模。设计锁在开始阶段都很粗,但当锁的争用问题变得严重时,设计就向更加细的加锁方向发展。

  内核提供两组原子操作的接口,一个针对整数,一个针对位进行操作。针对整数的原子操作只能对atomic_t类型的数据进行处理,原子位操作是对普通的指针进行的操作,可以操作任何希望的数据。

  自旋锁只能被一个可执行线程持有。一个被争用的自旋锁使得请求它的线程在等待锁重新可用时处于忙循环(自旋),浪费处理器时间。信号量是一种睡眠锁,同时允许任意数量的锁持有者,只有一个持有者的信号量叫互斥信号量。一个被争用的信号量使得请求它的线程进入一个队列,然后让其睡眠,处理器就可重获自由。所以1.自旋锁的作用是在短期内进行轻量级加锁,而信号量适用于锁被长时间持有的情况;2.自旋锁可用使用在中断程序中,在中断程序中使用自旋锁前,一定要禁止本地中断,否则中断程序就会打断正持有锁的内核代码并有可能去试图争用这个已经被持有的锁,从而导致双重请求死锁;信号量不可用在中断上下文。
  

/加锁一个自旋锁函数
void spin_lock(spinlock_t *lock);                   //获取指定的自旋锁
void spin_lock_irq(spinlock_t *lock);               //禁止本地中断获取指定的锁
//释放一个自旋锁函数
void spin_unlock(spinlock_t *lock);                 //释放指定的锁
void spin_unlock_irq(spinlock_t *lock);             //释放指定的锁,并激活本地中断

struct semaphore类型用来表示信号量。down一个信号量等于获取该信号量,临界区操作完成后,up释放信号量。

down最常见函数原型:

int down_interruptible(struct semaphore *sem)
{
        unsigned long flags;
        int result = 0;

        spin_lock_irqsave(&sem->lock, flags);
        if (likely(sem->count > 0))             //count是使用者数量,若count大于0则可获得信号量锁
                sem->count--;                   //获得信号量减1
        else
                result = __down_interruptible(sem); //没有获得信号量任务会被放入等待队列,在sem的wait_list链表尾部加入一新的节点
        spin_unlock_irqrestore(&sem->lock, flags);

        return result;                     //函数返回,其调用者开始进入临界区
}

up函数原型:

void up(struct semaphore *sem)
{
        unsigned long flags;

        spin_lock_irqsave(&sem->lock, flags);
        if (likely(list_empty(&sem->wait_list)))//wait_list队列为空表明没有其他进程正在等待该信号量
                sem->count++;
        else
                __up(sem);  //wait_list队列不为空说明有其他进程正睡眠,调用__up(sem)删除wait_list链表中的第一个有效节点,并唤醒睡眠在该节点上的进程,唤醒同时会获得该信号量
        spin_unlock_irqrestore(&sem->lock, flags);
}

在一个时刻仅允许有一个锁持有者,即count为1,这样的信号量称为互斥信号量。互斥体是一种互斥信号。互斥体用于保护共享的易变代码。struct mutex 类型用来表示互斥体。如果静态声明一个count=1的semaphore变量,可以使用DECLARE_MUTEX(name),而如果要定义一个静态mutex型变量,应该使用DEFINE_MUTEX。mutex上的P,V操作:

void mutex_lock(struct mutex *)
void mutex_unlock(struct mutex *)

互斥体和信号量的区别:

1.互斥量用于线程的互斥,信号量用于线程的同步。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。

2.互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

猜你喜欢

转载自www.cnblogs.com/fungi/p/11747349.html
今日推荐