分类: LINUX
2011-06-05 10:59:58
linux内核学习小结(上)
1.内核概述
内核种类:
2种:宏内核和微内核
linux是宏内核.很多功能都包括在内核中,没有通信的消耗.
微内核的特点是分功能,每个功能是一个服务器,之间要通信.
内核开发的特点:
不用clib;必须使用gnu c;没有进程中类似的内存保护;内核栈小;有同步和并发(支持中断/进程抢占/内核抢占/smp);
cpu任何时候都在以下三个点之一上:
1.内核空间,进程上下文,代表进程的执行.
2.内核空间,中断上下文,代表中断的执行.
3.用户空间,代表进程的执行.
2.进程
进程是处于执行中的程序实例以及它所包含的资源的总和.进程提供了两种虚拟机制:虚拟处理器和虚拟内存.
在内核中记录每个进程用8k内存,该内存含本进程的内核栈和threadinfo,threadinfo中含指向task结构的指针.
进程状态:
1.运行
2.可中断
3.不可中断
4.僵死
5.停止
进程的创建:
fork是创建新进程,exec是载入新可执行文件并执行
1.fork()是让子进程复制所有父进程的页表项方式来同享内存空间,写时复制.创建时传入VM标志
2.vfork()是不复制表项,常见时传入VFORK标志+VM标志.新子进程先执行,父进程sleep,直到子进程exit时才让父进程继续执行.
进程调度:
调度时,总是选择时间片未用完且最高优先级的进程.
进程分为cpu型和io型.linux优先调度io型进程.
0..99是实时进程,100..139(对应nice:-20..19)是普通进程.
当所有进程的时间片都用完,将重新计算所有进程时间片.
重新计算时间片时仅根据静态优先级(即nice).
每个cpu有一个运行队列,每个运行队列有2个优先级数组:一个是活跃,一个是过期.
1.动态优先级通过静态优先级(即nice)和进程交互性得出,如果是交互强则nice减小些.
2.如果是交互强的进程则即使时间片耗尽也不放到过期中.
切至过期的条件:
1.活动中无进程
2.过期已经非常饥饿了,即好久没调度了.
用户抢占时机:
1.从系调或中断返回到用户空间时(包括进程时间片耗尽时)
内核抢占时机:
1.从中断返回到内核时且内核锁(即抢占计数器)计数为0
2.释放内核锁时,发现抢占计数器减至0时
3.显式调度时
4.内核中的进程堵塞
实时进程:
fifo的进程不使用时间片,独占cpu,直到它主动释放.
rr的进程使用时间片,在时间片耗尽后调度同优先级的其它rr进程.
实时进程没有动态优先级,完全依赖静态优先级.
3.系统调用:
系统调用通过栈传参数.
在内核处理系统调用出错时,会将错误码写到errno全局变量.由perror()库函数解析给用户.
系统调用应该是可重入的.因为某些系调可以堵塞在内核,之后内核又处理其它进程的同一系调.
4.中断
注册中断处理程序时指定标志:
SA_INTERRUPT:表示快速处理,禁止所有中断.如果没有置位,就是默认的,即
屏蔽该处理程序正在处理的中断线.仅系统定时器用此标志.
SA_SHIRQ:多个处理程序共享该该中断线.在给定线上注册的所有处理程序应该都有此标志.
pci设备都是支持中断线共享的,故:
基于共享的设备而言,irq(即中断线)+devid共同指定一个具体的处理程序.
同一中断程序无需可重入,因为其会得到串行化处理.
在处理中断程序中,还会收到中断,可能是:
1.来自本cpu的同一中断线
2.来自其他cpu的同一中断线
在一个给定中断处理程序正在执行时,相应的中断线在所有cpu上都是禁止的(在
ack该中断时即禁止该中断线了),然后通过自旋锁防止其它cpu处理该中断.
但是,在处理程序内部会开中断,继续接收同一中断的重复或不同中断的嵌套.
中断栈维护独立的中断上下文,每个cpu都有一个.
从中断返回时,做检查
1.如果调度标志置1且是要返回到用户空间,则调度一次.
2.返回到内核,且抢占计数器为0,则也调度一次,在调度后,继续执行内核.
in_interrupt():中断程序中或下半部中.
in_irq():中断程序中.
5.下半部
上半部的特点是对时间和硬件有要求,且不能被其它中断(主要是同一中断)打断.
其它的就归于下半部.
BH(废弃)->任务队列(废弃)->软中断->工作队列
BH(废弃):每个bh在全局同步,即即使2个不同cpu也不能同时运行任何2个bh(即使不一样).
软中断:一个软中断不能抢占另一个软中断,相同类型的中断可以同时在多个cpu上运行.所以
软中断处理内部的变量都得好好保护,一般不需在多cpu间加锁,因为都是采取单cpu数据.
检查软中断的时机:1.硬中断处理返回时2.在ksoftirqd中3.显式检查
读pending位组时禁止本地中断,读完后开本地中断,防止位组被其它软中断改写.
在进入软中断处理函数时,禁止本地软中断,这样就串行化了,此轮仅处理已经读取的,但是其它cpu可以处理.
在中断处理完触发软中断前要禁止本地中断,防止多个中断改写同一变量.
引入软中断,是需要在多个cpu上同时运行同一个软中断.
tasklet:2个不同的tasklet可在2个不同cpu上同时执行,相同的tasklet不能在各个cpu上同时执行.
每个tasklet结构中有SCHED和RUN标志,SCHED标志表示已经调度(用于多cpu之间的同步),
RUN标识该tasklet正在运行.
调度某个tasklet:发现该tasklet已经置为SCHED则退出,因为已经调度了,只是没执行.
禁止本地中断(保护本cpu的task链表),把要调度的tasklet加到本cpu的task链表中,唤起软中断,然后开本地中断.
处理tasklet:禁止中断(保护本cpu的task链表),将本cpu的tasklet链表记到局部变量后清空,然后开中断.
循环处理每一个tasklet:如RUN标志没置位则置位RUN,如果RUN已经置位,则不处理,由其它cpu处理即可.执行完该tasklet后
请它的RUN标志.
ksoftirqd:
有2个方案
1.只要还有被触发的软中断就一直处理.
2.不处理重复触发的软中断.等其下一轮处理.
linux选用2,即当大量软中断触发时,唤醒ksoftirqd内核线程处理:
只要有pending,该线程一直处理.
工作队列:
由于软中断和tasklet在处理中都不能睡眠,故引入工作队列.
内核线程即工作者线程,但是使用进程上下文,所以可以睡眠.
每个cpu有一个数组,每个元素是一个工作队列,其中挂有工作链表.
下半部的加锁:
同一个类型tasklet,不管在同一cpu还是不同cpu都不能同时执行,所以其数据
无需考虑加锁问题,但是如果不同类型的tasklet之间共享数据,则要考虑加锁.
即使相同类型的软中断也可能有多个实例在同时执行,故其共享数据要加锁.
如果进程和下半部共享数据,则在进程中访问数据时要禁止下半部.
如果中断和下半部共享数据,则在访问时要禁止中断.
禁止所有下半部(包括软中断和tasklet):local_bh_disable()
开启本地下半部:local_bh_enable()