20169207《Linux内核原理与分析》第七周作业

这周作业基本分为两个方面,第一方面,阅读学习教材「Linux内核设计与实现 (Linux Kernel Development)」第教材第9,10章。第二方面.学习MOOC「Linux内核分析」第五讲「程序和进程」,并完成实验楼上配套实验五。在学习MOOC网视频的时候主要有以下收获。
首先在指定路径下打开kernel/include/linux/sched.h,查看源代码,如图:

接下来我们分析task_struct的数据结构
'''

struct task_struct{
volatile long state; //进程的运行状态
void *stack; //进程的内核堆栈
atomic_t usage;
unsigned int flags; //进程的一些标识符
unsigned int ptrace;
...
int on_rq;
...
struct sched_dl_entity dl;//和优先级,调度相关的,运行队列。
...
struct list_head; // 进程的链表。(内部为双向链表)
struct mm_struct mm,active_mm;(进程的地址空间,内存管理。)

'''
分析了task_struct的代码后,我们进入Linuxkernel/linux-3.18.6/kernel/fork.c来研究进程创建的一些代码。
首先依次查看了fork,vfork,clone的源代码,我们发现,他们最底层调用的都是do_fork方法。
fork代码截图:

vfork代码截图:

clone代码截图:

由于他们的底层调用都是do_fork方法,所有我们就对do_fork的代码进行了分析。


分析发现。do_fork里面copy_process主要是用来创建进程的。代码如下:

对子进程的初始化都包括:初始化文件,初始化文件系统,初始化sighand,初始化signal,初始化内存,初始化io。copy——process主要是拷贝内核堆栈数据和指定新进程的第一条指令地址。因为子进程的返回值是零,所以拷贝完还需要修改一下内核栈里压入的数据。
接下来是做实验,在实验过程中,需要删除menu文件夹,并克隆一个新的menu,用到的命令是 git clone https://github.com/mengning/menu.git 在实际操作过程中,遇到这个问题,unable to access 'https://github.com/mengning/menu.git/':Could not resolve host:github.com
遇到这个问题在网上寻求解决办法,首先第一步 ping github.com 访问到他的ip地址。然后第二步,在/etc/hosts中添加一行如下: “具体的ip” github.com。
但在ping github。com时提示unknown host github.com 。ping 百度时遇到了同样的问题。最后发现是实验楼环境不能联网的原因。
重新实验过程最后在自己虚拟机的环境中成功实现。

分析其代码:

按c之后开始启动内核。按n单步执行。s进入函数,finish结束掉当前函数。

在课本9.10章的阅读理解过程中,内核同步章节,我们理解了临界访问会产生数据的不安全性,避免并发和防止竞争条件称为同步。
造成并发执行的原因:


>中断--中断几乎可以在任何时刻异步发生,也就可能随时打断当前执行的代码。
>软中断和tasklet--内核能在任何时刻唤醒或调试软中断和tasklet,打断当前正在执行的代码。
>内核抢占--因为内核具有抢占性,所以内核中的任务可能会被另一任务抢占。
>睡眠及与用户空间的同步--在内核执行的进程可能会睡眠,这就会唤醒调度程序,从而导致调度一个新的用户进程执行。
>对称多处理--两个或多个处理器可以同时执行代码。
预防死锁的一些简单规则:
>按顺序加锁。使用嵌套的锁时必须保证一相同的顺序获取锁,这样可以阻止致命拥抱类型的死锁。
>防止发生饥饿。持续问自己,这个代码的执行是否一定会结束。
>不要重复请求同一个锁。
>设计应力求简单->越复杂的加锁方案越有可能造成死锁。

内核同步和同步方法

内核同步

防止共享资源并发访问是因为如果有多个执行线程同时访问和操作数据,可能发生各线程之间相互覆盖共享数据的情况,造成被访问数据处于不一致态。
临界区是访问和操作共享数据的代码段,为了避免临界区中并发访问,必须保证这些代码原子地执行,即执行结束前不可被打断。
避免并发和防止竞争条件称为同步。
对于单个变量,内核提供的实现原子操作的借口可以防止他们被并发访问。对于数据结构,需要确保一次有且只有一个线程对数据结构进行操作,或者当另一个线程在对临界区标记时,禁止其他访问,即锁提供的机制。锁的使用是资源的、非强制的,它是采用原子操作实现的。
用户空间要进行同步是因为用户程序会被调度程序抢占和重新调度。
内核中可能造成并发执行的原因有:中断、软中断、tasklet、内核抢占、睡眠及与用户空间的同步、对称多处理。在中断处理程序中能避免并发访问的安全代码称作中断安全代码,在对称多处理器机器中能避免并发访问的安全代码称为SMP安全代码,在内核抢占时能避免并发访问的安全代码称为抢占安全代码。
大多数的内核数据结构都需要加锁,是给数据加锁而并非代码。
简单来说,同步就是为了防止多个执行线程未结束前同时访问某个资源。

死锁

死锁是基于并发和同步产生的一种现象。它产生需要一定条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用了。
预防死锁的发生十分重要,一些规则对避免死锁有很大帮助,如:按顺序加锁、防止发生饥饿、不要重复请求同一个锁、设计力求简单。

内和同步方法

1、原子操作

原子操作是其他同步方法的基石。原子操作可以保证指令以原子方式执行且不被打断。我自己的理解就是原子操作在单处理器机器上防止了需要相同资源的操作并发,在多处理器机器上,防止需要相同资源的操作的并发以及并行。
内核提供了两组原子操作接口——一组指针对整数进行操作,另一组指针对单独的位进行操作。针对整数的原子操作只能对atomic_t类型的数据进行处理,该数据类型只能当做24位用,因为在SPARC体系结构上原子操作的32位int类型低8位嵌入了一个锁。在64位体系结构中,64位的原子变量要使用atomic64_t。
原子操作通常是内联函数,往往是通过内嵌汇编指令实现的。本来就是原子的函数往往被定义成一个宏。原子操作只保证原子性,却不保证顺序性,顺序性通过屏障指令来实施。
原子位操作函数是对普通的内存地址进行操作的,参数是一个指针和一个位号。与原子整数操作不同,代码一般无法选择是否使用位操作,它们是唯一的、具有可移植性的设置特定位的方法。

2、自旋锁

Linux内核中最常见的的锁是自旋锁。自旋锁最多只能被一个可执行进程持有,它可以防止多于一个的执行线程同时进入临界区。
自旋锁的实现和体系结构密切相关,代码往往通过汇编实现。其基本使用形式如下:
DEFINE_SPINLOCK(mr_lock);
spin_lock(&mr_lock);
/临界区.../
spin_unlock(&mr_lock);

自旋锁是不可递归的!显而易见,如果你试图获得一个你正持有的锁,就必须自旋,等待自己释放这个锁,而自己又正处于忙等待中,没有机会释放锁,就会产生自死锁。
在中断处理程序中使用自旋锁时,一定要在获取锁之前禁止本地中断,否则中断处理程序会打断正持有锁的内核代码,它也可能会去争用该已被持有的锁从而自旋,但锁的持有者在中断处理程序执行完之前不能运行,就会产生双重请求死锁。

3、信号量

Linux中信号量是一种睡眠锁。试图获得一个不可用的信号量时,信号量会将其推进一个等待队列,让其睡眠直到有信号量可用时被唤醒。信号量是唯一允许睡眠的锁。
自旋锁会使试图获得已被持有的锁的进程进入忙等待,信号量会使试图获得不可用的信号量的进程进入睡眠,信号量和自旋锁的明显不同使得两种同步方法适用范围不同。信号量适用于锁会被长时间持有的情况,而自旋锁适用于锁被短时间持有;执行线程在锁被争用时会睡眠,因此只能在进程上下文中才能使用,因为中断上下文中不会进行调度,使得即使锁被释放,因为争用该锁进入睡眠的进程也不会被唤醒;占用信号量时不能占用自旋锁,因为持有自旋锁时不允许睡眠和被抢占。信号量不会禁止内核抢占,持有信号量的代码可以被抢占。

4、互斥体

互斥体(mutex)是指任何可以睡眠的强制互斥锁,相当于一个不使用计数的简化的信号量。互斥体是一种信号。对于mutex:
•任何时刻只有一个任务可以持有mutex,上锁者必须负责解锁;

•递归的上锁和解锁是不允许的;

•持有一个mutex的进程不可以退出;

•mutex不能在中断或者下半部中使用(跟信号量一样);

•mutex只能通过官方API 管理。

5、其他同步方法

内和同步方法还有完成变量、大内核锁、顺序锁、禁止抢占和屏障

在内核同步方法章节:我们指导了Linux提供的同步方法,这些方法使得内核开发者们能编写出高效而又自由竞争的代码。先后介绍了原子操作,自旋锁,读——写自旋锁,信号量,互斥体,完成变量,BLK:大内核锁,顺序锁,禁止抢占,顺序和屏障。。。等等方法。这使得我能理解Linux中用于同步和开发的具体办法。

原文地址:https://www.cnblogs.com/littletang/p/6031420.html