
Linux内核设计与实现
cxf100900
这个作者很懒,什么都没留下…
展开
-
9.9 内核同步方法_禁止抢占
内核抢占代码使用自旋锁作为非抢占区域的标记。如果一个自旋锁被持有,内核便不能进行抢占。 可以通过preempt_disable()禁止内核抢占。这是一个可以嵌套调用的函数,可以调用任意次。每次调用都必须有一个相应的preempt_enable()调用。当最后一次preempt_enable()被调用,内核抢占才重新启用。原创 2010-10-19 00:30:00 · 702 阅读 · 0 评论 -
7.4 下半部和推后执行的工作_工作队列
<br /> 工作队列可以把工作推后,交由一个内核线程去执行--这个下半部分总是会在进程上下文执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许重新调度甚至是睡眠。<br /> 通常,在工作队列和软中断/tasklet中作出选择非常容易。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择软中断或tasklet。<br /> 如果你需要用一个可以重新调度的实体执行你的下半部处理,你应该使用工作队列。它是唯一能在进程上下原创 2010-08-27 00:20:00 · 1181 阅读 · 0 评论 -
7.2 下半部和推后执行的工作_软中断
7.2.1 软中断的实现 软中断是在编译期间静态分配的。它不像tasklet那样能被动态地注册或去除。软中断由softirq_action结构表示,它定义在:struct softirq_action{ void (*action) (struct softirq_action *); void *data;} 1. 软中断处理程序 一个软中断不会抢占另外一个软中断。实际上,唯一可以抢占软中断的是中断处理程序。不过,其他的软中断--甚至是相同类型的软中断--可以在其他处理器上同时执行原创 2010-08-25 22:33:00 · 796 阅读 · 1 评论 -
6.7 中断和中断处理程序_中断控制
<br /> 一般来说,控制中断系统的原因归根到底是需要提供同步。<br /> 6.7.1 禁止和激活中断<br /> 用于禁止当前处理器(仅仅是当前处理器)上的本地中断,随后又激活它们的语句为:local_irq_disable();load_irq_enable();<br /> 如果在调用local_irq_disable()例程之前已经禁止了中断,那么该例程往往会带来潜在的危险;同样相应的load_irq_enable()例程也存在潜在危险,因为它将无条件地激活中断,尽管这些中断可原创 2010-08-05 00:45:00 · 707 阅读 · 0 评论 -
6.8 中断和中断处理程序_别打断我,马上结束
<br /> 中断是一种由设备使用的硬件资源异步地向处理器发信号。实际上,中断就是由硬件来打断操作系统。<br /> 内核提供的接口包括注册和注销中断处理程序,禁止中断,屏蔽中断线,以及检查中断系统的状态。原创 2010-08-04 00:54:00 · 508 阅读 · 0 评论 -
6.6 中断和中断处理程序_中断处理机制的实现
<br /> 中断处理系统在Linux中的实现是非常依赖于体系结构的。<br /> SA_INTERRUPT表示处理程序必须在中断禁止的情况下运行。<br /> /proc/interrupts原创 2010-08-04 00:36:00 · 337 阅读 · 0 评论 -
6.5 中断和中断处理程序_中断上下文
<br /> 当执行一个中断处理程序或下半部时,内核处于中断上下文中。进程上下文是一种内核所处的操作模式,此时内核代表进程执行--例如,执行系统调用或运行内核线程。因为进程是以进程上下文的形式连接到内核中的,因此,在进程上下文可以睡眠,也可以调用调度程序。<br /> 如果一个函数睡眠,就不能在你的中断处理程序中使用它。<br /> 中断上下文具有较为严格的时间限制,因为它打断了其他代码。中断上下文中的代码应当迅速简洁,尽量不要使用循环去处理繁重的工作。<br /> 中断处理程序栈的原创 2010-08-04 00:25:00 · 424 阅读 · 0 评论 -
6.4 中断和中断处理程序_编写中断处理程序
<br /> 以下是一个典型的中断处理程序声明:static irqreturn_t intr_handler(intirq, void *dev_id, struct pt_regs *regs)<br /> 第一个参数irq就是这个处理程序要响应的中断的中断线号。<br /> 第二个参数dev_id是一个通用指针,它与在中断处理程序注册时传递给request_irq()的参数dev_id必须一致。如果该值有唯一确定性(建议采用这样的值,以便支持共享),那么它就相当于一个cookie,可原创 2010-08-04 00:16:00 · 740 阅读 · 0 评论 -
6.3 中断和中断处理程序_注册中断处理程序
<br /> 驱动程序可以通过下面的函数注册并激活一个中断处理程序,以便处理中断:int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long irgflags, const char *devname, void *dev_id)<br /> 第一个参数irq表示要分配的中断号。<br /> 第二个参数handler是一个指针,指向处理这个中断的原创 2010-08-03 22:54:00 · 970 阅读 · 0 评论 -
7.1 下半部和推后执行的工作_下半部
<br /> 下半部的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作。记住,中断处理程序会异步执行,并且在最好的情况下它也会锁定当前的中断线。对于在上半部和下半部直接按划分工作,尽管不存在某种严格的规则,但还是有一些提示可供借鉴:如果一个任务对时间非常敏感,将其放在中断处理程序中执行。如果一个任务和硬件相关,将其放在中断处理程序中执行。如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。其他所有任务,考虑放置在下半部执行。<br /> 7.1.1 为什么要原创 2010-08-25 00:51:00 · 538 阅读 · 0 评论 -
7.2 下半部和推后执行的工作_tasklet
7.3.1 tasklet的实现<br /> 因为tasklet是通过软中断实现的,所以它们本身也是软中断。tasklet由两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。这两者之间唯一的实际区别在于HI_SOFTIRQ类型的软中断先于TASKLET_SOFTIRQ类型的软中断执行。<br /> 1. tasklet结构体<br /> tasklet有tasklet_struct结构表示。每个结构体单独代表一个tasklet,它在<linux/interrupt.h>中定义原创 2010-08-26 00:34:00 · 691 阅读 · 0 评论 -
8 内核同步介绍
8.1 临界区和竞争条件<br /> 所谓临界区就是访问和操作共享数据的代码段。如果两个执行线程有可能处于同一个临界区中,就称它是竞争条件。避免并发和防止竞争条件被称为同步。<br /> 8.2 加锁<br /> 各种锁机制之间的区别主要在于当锁被争用时(已经被使用)的行为表现--一些锁会简单地执行忙等待,而有些锁会使当前任务睡眠直到锁可用为止。<br /> 8.2.1 到底是什么造成了并发执行<br /> 用户空间之所以需要同步,是因为用户程序会被调度程序抢占和重新调度。<br />原创 2010-10-09 00:39:00 · 473 阅读 · 0 评论 -
9.5 内核同步方法_读-写信号量
<br /> 所有的读-写信号量都是互斥信号量(也就是说,它们的引用计数等于1)。所有读-写锁的睡眠都不会被信号打断,所以它只有一个版本的down()操作。<br /> 读-写信号量相比读-写自旋锁多一种特有的操作:downgrade_writer()。这个函数可以动态地将获取的写锁转换为读锁。原创 2010-10-19 00:23:00 · 486 阅读 · 0 评论 -
9.4 内核同步方法_信号量
<br /> Linux的信号量是一种睡眠锁。如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进一个等待队列;,然后让其睡眠。当持有信号量的进程将信号量释放后,处于等待队列中的那个任务将被唤醒,并获得该信号量。由于执行线程在锁被争用时会睡眠,所以只能在进程上下文中才能获取信号量锁,因为在中断上下文是不能进行调度的。在占用信号量的同时不能占用自旋锁。因为在等待信号量时可能会睡眠,而在持有自旋锁时是不允许睡眠的。<br /> 信号量不同于自旋锁,他不会禁止内核抢占,所以持有信号量的代码原创 2010-10-19 00:15:00 · 458 阅读 · 0 评论 -
9.3 内核同步方法_读-写自旋锁
<br /> 一个或多个读任务可以并发的持有读者锁;相反,用于写的锁最多只能被一个写任务持有,而且此时不能有并发的读操作。有时把读/写锁叫做共享/排斥锁,或者并发/排斥锁,因为这种锁以共享(对读者而言)和排斥(对写者而言)的形式获得使用。<br /> 注意不能把一个读锁“升级”为写锁。这种代码:read_lock(&mr_rwlock);write_lock(&mr_rwlock);<br /> 将会带来死锁,因为写锁会不断自旋,等待所有的读者释放锁,其中也包括它自己。所以当确实需要写操作原创 2010-10-19 00:06:00 · 427 阅读 · 0 评论 -
9.2 内核同步方法_自旋锁
<br /> Linux内核中最常见的锁是自旋锁。自旋锁最多只能被一个可执行线程持有。<br /> 自旋锁不应该被长时间持有。持有自旋锁的时间最好小于完成两次上下文切换的耗时。<br /> 警告:自旋锁是不可递归的!<br /> 自旋锁可以使用在中断处理程序中(此外不能使用信号量,因为它们会导致睡眠)。在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁止本地中断(当前处理器上的中断请求),否则,中断处理程序就会打断正持有锁的内核代码,有可能试图去争用这个已经被持有的自旋锁。注意,需要原创 2010-10-18 23:57:00 · 398 阅读 · 0 评论 -
9.10 内核同步方法_顺序和屏障
rmb()方法提供了一个“读”内存屏障,它确保跨越rmb()的载入动作不会发生重排序。也就是说,在rmb()之前的载入操作不会被重新排在该调用之后,同理,在rmb()之后的载入操作不会被重新排在该调用之前。 wmb()方法提供了一个“写”内存屏障,这个函数的功能和rmb()类似,区别仅仅是它是针对存储而非载入--它确保跨越屏障的存储不发生重排序。 mb()方法既提供了读屏障也提供了些屏障。原创 2010-10-19 00:35:00 · 688 阅读 · 0 评论 -
9.6 内核同步方法_自旋锁和信号量
<br /> 因为在中断上下文中只能使用自旋锁,而在任务睡眠时只能使用信号量。原创 2010-10-19 00:24:00 · 499 阅读 · 0 评论 -
9.7 内核同步方法_完成变量
<br /> 如果在内核中一个任务需要发出信号通知另一个任务发生了某个特定事件,利用完成变量是使两个任务得以同步的简单方法。原创 2010-10-19 00:26:00 · 506 阅读 · 0 评论 -
9.1 内核同步方法_原子操作
<br /> 内核提供了两组原子操作接口--一组对整数进行操作,另一组对单独的位进行操作。<br /> 9.1.1 原子整数操作<br /> 针对整数的原子操作只能对atomic_t类型的数进行处理。在这里之所以引入了一个特殊数据类型,而没有直接使用C语言的int类型,主要是出于两个原因:首先,让原子函数只接受atomic_t类型的操作数,可以确保原子操作只与这种特殊类型数据一起使用。其次,使用atomic_t类型确保编译器不对相应的值进行访问优化--这点使得原子操作最终接收到正确的内存地址,而原创 2010-10-18 23:37:00 · 429 阅读 · 0 评论 -
6.2 中断和中断处理程序_中断处理程序
<br /> 在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序或中断服务例程。中断处理程序通常不是和特定设备关联,而是和特定中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该设备就可以对应多个中断处理程序,相应的,该设备的驱动程序也就需要准备多个这样的函数。一个设备的中断处理程序是它设备驱动程序的一部分--设备驱动程序是用于对设备进行管理的内核代码。<br /> 中断处理程序与其他内核函数的真正区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于我们称之原创 2010-08-03 22:13:00 · 613 阅读 · 0 评论 -
6.1 中断和中断处理程序_中断
<br /> 不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识。这些中断值通常被称为中断请求线。对于连接在PCI总线上的设备而言,中断时动态分配的。<br /> 异常<br /> 异常与中断不同,它在产生时必须考虑与处理器时钟同步。实际上,异常也常常称为同步中断。在处理器执行到由于编程失误而导致的错误指令(例如被0除)的时候,或者是在执行期间出现特殊情况(例如缺页),必须靠内核来处理的时候,处理器就会产生一个异常。原创 2010-08-03 21:44:00 · 450 阅读 · 0 评论 -
4.2 进程调度_Linux调度算法
<br /> 设计新的调度程序是为了实现下列目标:充分实现O(1)调度。不管有多少进程,新调度程序采用的每个算法都能在恒定时间内完成。强化SMP的亲和力。尽量将相关一组人物分配给一个CPU进行连续的执行。只有在需要平衡任务队列的大小时才在CPU之间移动进程。加强交互性能。即使在系统处于相当负载的情况下,也能保证系统的响应,并立即调度交互式进程。保证公平。在合理设定的时间范围内,没有进程会处于饥饿状态。同样的,也没有进程能够显失公平地得到大量时间片。虽然最常见的优化情况是系统中只有1~2个可运行进程,但原创 2010-07-29 22:53:00 · 1330 阅读 · 0 评论 -
4.1 进程调度_策略
<br /> 策略决定调度程序在何时让什么进程运行。调度器的策略往往决定系统的整体印象,并且,还要负责优化使用处理器时间。<br /> 4.1.1 I/O消耗型和处理器消耗型的进程<br /> 进程可以被分为I/O消耗型和处理器消耗型。前者指进程的大部分时间用来提交I/O请求或是等待I/O请求。因此,这样的进程经常处于可运行状态,但通常都是运行短短的一会儿,因为它在等待更多的I/O请求时最后总会阻塞。相反,处理器消耗型进程把时间大多用在执行代码上。除非被抢占,否则它们通常都一直不停地运行。<br原创 2010-07-28 22:49:00 · 411 阅读 · 0 评论 -
4 进程调度
进程调度,这使进程有效工作的一组代码。在一组处于可运行状态的进程中选择一个来执行,是调度程序所需要完成的基本工作。 多任务系统可以划分为两类:非抢占式和抢占式。强制的挂起动作就叫做抢占。进程在被抢占之前能够运行的时间是预先设置好的,而且有一个专门的名字,叫进程的时间片。时间片实际上就是分配给每个可运行进程的外处理器时间段。有效管理时间片能使调度程序从系统全局的角度做出调度决定,这样做还可以避免个别进程独占系统资源。 进程主动挂起自己的操作称为让步。原创 2010-07-28 22:18:00 · 281 阅读 · 0 评论 -
3.3 进程管理_线程在Linux中的实现
<br /> 线程机制提供了在同一程序内共享内存地址空间运行的一组线程。线程仅仅被视为一个与其他进程共享某些资源的进程。线程的创建和普通进程的创建类似,只不过在调用clone()的时候需要传递一些参数标志来指明需要共享的资源。<br /> 内核线程<br /> 内核线程和普通的进程间的区别在于内核线程没有独立的地址空间(实际上它的mm指针被设置为NULL)。它们只在内核空间运行,从来不切换到用户空间去。内核进程和普通进程一样,可以被调度,也可以被抢占。原创 2010-07-24 00:32:00 · 318 阅读 · 0 评论 -
3.5 进程管理_进程小结
<br /> Linux如何存放和表示进程--用task_struct和thread_info,如何创建进程--通过clone()和fork(),如何把新的执行映像装入到地址空间--通过exec()系统调用族,如何表示进程的层次关系,父进程又是如何收集其后代的信息--通过wait()系统调用族,以及进程最终如何死亡--强制或自愿地调用exit()。原创 2010-07-24 00:59:00 · 281 阅读 · 0 评论 -
3.4 进程管理_进程终结
<br /> 当一个进程终结时,内核必须释放它所占有的资源并把这一不幸告知其父进程。<br /> 一般说来,进程的析构发生在它调用exit()之后,既可能显示地调用这个系统调用,也可能隐式地从某个程序的主函数返回(其实C语言编译器会在main()函数的返回点后面放置调用exit()的代码)。<br /> 3.4.1 删除进程描述符<br /> 在调用了do_exit()之后,尽管线程已经僵死不能再运行了,但是系统还保留了它的进程描述符。这样做可以让系统有办法在子进程终结后仍能获得它的信息。原创 2010-07-24 00:53:00 · 338 阅读 · 0 评论 -
3.2 进程管理_进程创建
子进程与父进程的区别仅仅在于PID(每个进程唯一)、PPID(父进程的进程号、子进程将其设置为被拷贝进程的PID)和某些资源和统计量(例如挂起的信号,它没有必要被继承) 3.2.1 写时拷贝 Linux的fork()使用写时拷贝页实现。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。fork()的实际开销就是复制父进程的页表以及给予进程创建唯一的进程描述符。 3.2.2 fork() Linux通过原创 2010-07-24 00:16:00 · 535 阅读 · 0 评论 -
3 进程管理
<br /> 一个进程就是处于执行期的程序(目标码存放在某种存储介质上)。但进程并不仅仅局限于一段可执行程序代码(Unix称其为代码段(text section))。通常进程还要包含其他资源,像打开的文件、挂起的信号、内核内部数据、处理器状态、地址空间及一个或多个执行线程、当然还包括用来存放全局变量的数据段等。<br /> 执行线程,简称线程,是在进程中活动的对象。每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。内核调度的对象是线程,而不是进程。对Linux而言,线程只不过是一种特殊原创 2010-07-20 22:59:00 · 297 阅读 · 0 评论 -
4.4 进程调度_实时
<br /> Linux提供了两种实时调度策略:SCHED_FIFO和SCHED_RR。而普通的、非实时的调度策略是SCHED_NORMAL。SCHED_FIFO实现了一种简单的、先入先出的调度算法,它不使用时间片。SCHED_FIFO级的进程会比任何SCHED_NORMAL级的进程都先得到调度。一旦一个SCHED_FIFO级进程处于可执行状态,就会一直执行,直到它自己受阻塞或显式地释放处理器为止;它不基于时间片,可以一直执行下去。SCHED_RR与SCHED_FIFO大体相同,只是SCHED_RR级的原创 2010-07-30 00:47:00 · 441 阅读 · 0 评论 -
4.6 进程调度_调度程序小结
<br /> 本章考察了进程调度所遵循的基本原理、具体实现、调度算法以及目前Linux内核所使用的接口。原创 2010-07-30 01:02:00 · 371 阅读 · 0 评论 -
5 系统调用
<br /> 为了和用户空间上运行的进程进行交互,内核提供了一组接口。透过该接口,应用程序可以访问硬件设备和其他操作系统资源。<br /> 系统调用在用户空间进程和硬件设备之间添加了一个中间层。该层主要作用有三个。第一,它为用户空间提供了一种硬件的抽象接口。第二,系统调用保证了系统的稳定和安全。第三,每个进程都运行在虚拟系统中,而在用户空间和系统的其余部分提供这样的一层公共接口。在Linux中,系统调用时用户空间访问内核的唯一手段;除异常和陷入外,它们是内核唯一的合法入口。原创 2010-07-30 23:51:00 · 369 阅读 · 0 评论 -
5.6 系统调用_系统调用小结
<br /> 我们考察了Linux内核如何实现系统调用,以及执行系统调用的连锁反应:陷入内核,传递系统调用号和参数,执行正确的系统调用函数,并把返回值带回用户空间。原创 2010-07-31 00:55:00 · 318 阅读 · 0 评论 -
5.5 系统调用_系统调用上下文
<br /> 内核在执行系统调用的时候处于进程上下文。在进程上下文中,内核可以休眠(比如在系统调用阻塞或显式调用schedule()的时候)并且可以被抢占。这两点都很重要。<br /> 5.5.1 绑定一个系统调用的最后步骤5.5.2 从用户空间访问系统调用<br /> 5.5.3 为什么不通过系统调用的方式实现原创 2010-07-31 00:52:00 · 833 阅读 · 0 评论 -
5.4 系统调用_系统调用的实现
参数验证<br /> 在接收一个用户空间的指针之前,内核必须保证:指针指向的内存区域属于用户空间。进程决不能哄骗内核去读内核空间的数据。指针指向的内存区域在进程的地址空间里。进程决不能哄骗内核去读其他进程的数据。如果是读,该内存应被标记为可读。如果是写,该内存应被标记为可写。进程决不能绕过内存访问限制。 <br /> 注意,copy_to_user()和copy_from_user()都有可能引起阻塞。当包含用户数据的页被换出到硬盘上而不是在物理内存上的时候,这种情况就会发生。此时,进程就会休眠原创 2010-07-31 00:42:00 · 341 阅读 · 0 评论 -
4.5 进程调度_与调度相关的系统调用
<br /> Linux提供了一族系统调用,用于管理与调度程序相关的参数。这些系统调用可以用来操作和处理进程优先级、调度策略及处理器,同时还提供了显示的将处理器交给其他进程的机制。<br /> 4.5.1 与调度策略和优先级相关的系统调用<br /> 4.5.2 与处理器绑定有关的系统调用<br /> 4.5.3 放弃处理器时间<br /> Linux通过sched_yield()系统调用,提供了一种让进程显式地将处理器时间让给其他等待执行进程的机制。它是通过将进程从活动队列中移到过期队列中实现原创 2010-07-30 00:59:00 · 560 阅读 · 0 评论 -
4.3 进程调度_抢占和上下文切换
<br /> 上下文切换,也就是从一个可执行进程切换到另一个可执行进程。<br /> 内核提供了一个need_resched标志来表明是否需要重新执行一次调度。再返回用户空间以及从中断返回的时候,内核也会检查need_resched标志。每个进程都包含一个need_resched标志,这是因为访问进程描述符内的数值要比访问一个全局变量快。<br /> 4.3.1 用户抢占<br /> 内核即将返回用户空间的时候,如果need_resched标志被设置,会导致schedule()被调用,此时原创 2010-07-30 00:32:00 · 959 阅读 · 0 评论 -
5.3 系统调用_系统调用处理程序
<br /> 通知内核的机制是靠软中断实现的:通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序实际上就是系统调用处理程序。<br /> 5.3.1 指定恰当的系统调用<br /> 因为所有的系统调用陷入内核的方式都一样,所以仅仅是陷入内核空间是不够的。因此必须把系统调用号一并传给内核。<br /> system_call()函数通过将给定的系统调用号与NR_syscalls做比较来检查其有效性。如果它大于或者等于NR_syscalls,该函数就返回-ENOSYS原创 2010-07-31 00:29:00 · 716 阅读 · 1 评论 -
5.2 系统调用_系统调用
<br /> Unix系统调用在出现错误的时候会把错误码写入errno全局变量。通过调用perror()库函数,可以把该变量翻译成用户可以理解的错误字符串。<br /> 5.2.1 系统调用号<br /> 在Linux中,每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。<br /> 系统调用号相当关键:一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。此外,如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用,否则,以前编译过的代码会调用这原创 2010-07-31 00:17:00 · 394 阅读 · 0 评论