Linux内核设计与实现 读书笔记.docx
- 文档编号:7186821
- 上传时间:2023-01-21
- 格式:DOCX
- 页数:32
- 大小:33.88KB
Linux内核设计与实现 读书笔记.docx
《Linux内核设计与实现 读书笔记.docx》由会员分享,可在线阅读,更多相关《Linux内核设计与实现 读书笔记.docx(32页珍藏版)》请在冰豆网上搜索。
Linux内核设计与实现读书笔记
Linux内核设计与实现读书笔记
(1)1-7
第二章Linux内核
1内核开发特点
1)内核编译时不能访问C库;
2)浮点数很难使用;
3)内核只有一个定长堆栈;
4)注意同步和并发。
第三章进程管理
1current宏:
查找当前运行进程的进程描述符。
2进程状态(5种)
TASK_RUNNING:
1)正在运行;2)在运行队列中等待执行。
TASK_INTERRUPTIBLE:
进程正在睡眠,可以被信号唤醒。
TASK_UNINTERRUPTIBLE:
进程正在睡眠,不会收到信号被唤醒。
TASK_ZOMBIE:
僵死态,进程已经结束,父进程未使用wait4()。
TASK_STOPPED
3进程上下文
进程进入内核空间时,current宏依然有效,内核“代表进程执行”。
4进程创建
1)fork():
拷贝当前进程创建一个子进程。
2)exec():
读取可执行文件并载入地址空间开始运行。
3)写时拷贝(copy-on-wrtie):
推迟数据拷贝,在需要写入数据时,数据才会被复制。
4)vfork():
不拷贝父进程的页表项,子进程作为父进程的一个线程在它的地址空间运行,父进程被阻塞直至子进程退出,子进程不能向地址块空间写入数据。
5线程
Linux把所有的线程都当作进程来实现。
6内核线程:
独立运行在内核中的标准进程。
内核线程没有独立的地址空间,只能在内核空间中运行,创建内核线程用kernel_thread()。
7进程终结
1)释放资源;
2)进入TASK_ZOMBIE;
3)等待wait4()。
第四章进程调度
1多任务系统
非抢占式多任务:
主动让步
抢占式多任务(preemptive):
时间片
2进程
IO消耗型:
常常阻塞
处理器消耗型:
执行代码
3动态优先级调度方法
允许调度程序根据需要加减优先级。
两组优先级范围:
1)nice值:
-20至+19,默认值为0,nice值越大,优先级越低。
2)实时优先级:
0至99,任何实时进程优先级都高于普通进程。
4时间片
默认时间片为20ms。
进程时间片用完——进程到期——所有进程都到期——重新计算时间片
5可执行列队(runqueue):
每个处理器一个的可执行进程链表,还包含每个处理器的调度信息。
cpu_rq(processor):
返回给定处理器的可执行队列指针。
this_rq():
返回当前处理器的可执行队列。
task_rq(task):
返回给定任务所在的队列指针。
this_rq_lock():
锁住当前可执行列队。
rq_unlock():
释放给定列队上的锁。
★为了避免死锁,要锁住多个运行列队的代码,需要按同样的顺序获得这些锁。
6优先级数组
每个运行列队有两个优先级数组:
一个活跃的,一个过期的。
优先级数组包含一个优先级位图,共有140个优先级用了5个32位长整形保存。
活动数组:
可执行队列上的进程还有时间片剩余。
过期数组:
可执行队列上的进程耗尽了时间片。
★进程从活动数组移动至过期数组时重新计算时间片。
■重新计算时间片,以静态优先级为基础计算。
■为了判断Linux进程类型,Linux记录了进程用于休眠和执行的时间。
■若进程交互性非常强,时间片用完后,会被再次放入活动数组。
7休眠
休眠通过等待列队进程处理。
等待列队:
wait_queue_head_t
静态创建等待列队:
DECLARE_WAITQUEUE
动态创建等待列队:
init_waitqueue_head()
加入等待列队:
add_wait_queue()
移出等待列队:
remove_wait_queue()
8负载平衡
保证可执行列队之间的负载处于均衡状态(用于SMP)。
两种调用方法:
1)当前可执行列队为空;2)系统定时器200ms一次。
9上下文切换
1)将虚拟内存从上一个进程映射到新的进程中;
2)将上个进程处理器的状态切换到新的进程,保存、恢复栈信息和寄存器信息。
10schedule()被调用的时间
1)某个进程耗尽时间片时;
2)优先级高的进程进入可执行态时;
3)返回用户空间时;
4)中断返回时。
11内核抢占
只要没有持有锁,内核就可以进行抢占。
preempt_count计数器记录内核锁的数量。
内核抢占时机:
1)从中断返回内核空间时;
2)当内核代码再一次具有可抢占性的时候;
3)内核任务显示调用shedule();
4)内核任务阻塞。
12实时性
内核实时调度策略:
1)SCHED_FIFO:
简单,先进先出调度;
2)SCHED_RR:
时间片轮转调度,耗尽时间片后不再执行。
■SCHED_NORMAL是普通、非实时的调度策略。
■内核实时调度策略是基于静态优先级的:
内核不为实时进程计算动态优先级,实时优先级范围:
0-99。
■动态非实时优先级范围:
100-139(对应nice值-20至+19)。
第五章系统调用
1应用程序通过软中断机制通知内核。
2参数验证
指针:
1)指针指向的内存区域属于用户空间;
2)指针指向的内存区域属于本进程地址空间;
3)如果读,内存标记为可读;如果写,内存标记为可写。
3copy_to_user()的三个参数:
1)进程空间的目的地址内存;
2)内核空间源地址;
3)需要拷贝的数据长度(字节数)。
copybit_from_user()和上面相反。
第六章中断和中断处理程序
1上半部和下半部
上半部:
中断处理程序
下半部:
稍后完成的工作
2注册中断
intrequest_irq(irq,*handle,irqflag,*devname,*devid)
irq中断号。
handle一个指针,指向中断处理程序。
irqflag标志位:
SA_INTERRUPT快速中断处理程序(禁止其他中断);SA_SHIRQ:
可在多个中断处理程序间共享中断线。
devname与中断相关的设备的ASCII文本表示法。
dev_id用于共享中断线,当中断需要退出时,dev_id提供唯一的标志信息。
从共享中断线的中断处理程序中删除指定程序。
3释放中断
free_irq(unsignedintirq,void*dev_id)
4中断处理程序
irqreturn_tintr_handle(intirq,void*dev_id,structpt_regs*regs)
regs:
一个指向结构体的指针,包含中断处理之前的处理器寄存器和状态,regs使用越来越少,应该忽略。
返回值irqreturn_t:
1)IRQ_NONE:
中断对应设备并不是注册函数期间指定的产生源;
2)IRQ_HANDLE:
正常返回值。
重入问题:
Linux的中断处理程序无需考虑重入,当一个给定的中断处理程序正在执行,相应的中断会被屏蔽。
5中断上下文
★中断上下文不可以睡眠,不能从中断上下文中调用睡眠函数。
中断栈:
现在中断处理程序拥有自己的栈,每个处理器一个,大小为4KB。
6中断控制
local_irq_disable():
禁止当前处理器上的本地中断;
local_irq_enable():
允许中断;
local_irq_save(flag):
禁止中断并保存系统状态;
local_irq_restore(flag):
恢复中断并恢复到原来的状态;
disable_irq(unsignedintirq):
禁止指定中断线;
disable_irq_nosync():
不等待当前中断处理完毕就禁止指定中断;
enable_irq(unsignedintirq):
允许指定中断;
synchronize_irq():
等待一个特定的中断处理程序的退出。
★如果调用了两次disable_irq(),需要对应调用两次enable_irq()才可以恢复。
第七章下半部和推后执行的工作
1下半部
1)缩短中断执行时间;
2)下半部执行时允许响应所有的中断。
2下半部的类型
1)BH:
早期实现,现在已经淘汰;
2)任务列队(taskqueue);
3)软中断(softirq):
静态定义的下半部接口,共32个,可在所有处理上同时执行。
4)tasklet:
基于软终端的动态下半部实现,不同类型的tasklet可在不同处理器同时执行,相同类型的tasklet不可以同时执行。
5)工作列队(workqueue):
取代任务列队,对推后执行的工作排队。
6)内核定时器:
将操作推后到某个确定的时间段。
3软中断
1)软中断最多有32个。
2)一个软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序。
3)软中断的执行时机:
(1)从一个硬件中断代码返回时;
(2)在ksoftirq内核线程中;(3)显示检查和执行处理的软中断代码中,如网络子系统。
4)软中断保留给系统中对时间要求最严格和最重要的下半部使用:
如网络和SCSI。
4tasklet
1)tasklet通过软中断实现,本身也是软中断,有两种:
HI_SOFTIRQ(优先级较高)和TASKLET_SOFTIRQ。
2)的状态有:
0、TASKLET_STATE_SCHED(已经被调度,正等待运行)和TASKLET_STATE_RUN(正在运行,应用于多核系统)。
3)tasklet的调度过程:
(1)检查tasklet的状态是否为SCHED——>返回;
(2)保存中断,禁止本地中断;
(3)将调度tasklet加入tasklet_vec或者tasklet_hi_vec表头;
(4)唤起TASKLET_SOFTIRQ或HI_SOFTIRQ中断——>do_softirq();
(5)恢复中断并恢复原状态。
4)使用tasklet
(1)声明
静态创建
DECLARE_TASKLET
DECLARE_TASKLET_DISABLED
动态创建
tasklet_init(t,tasklet_handle,dev)
(2)handle
voidtasklet_handle(unsignedlongdata)
★由于tasklet靠软中断实现,所以tasklet执行中不能睡眠——>不能使用信号量或阻塞函数。
tasklet运行时运行中断,需要做好保护工作。
(3)调度
tasklet_schedule(&my_tasklet)加入调度列队
tasklet_disable()禁止莫个tasklet,若正在执行则等待执行完毕再禁止
tasklet_disable_nosync()立即禁止(不太安全)
tasklet_enable()激活一个tasklet
tasklet_kill()去掉列队的第一个tasklet
(4)ksoftirq辅助软中断内核线程
内核不会立即处理重新触发的软中断,当大量软中断出现时,内核会唤起一组线程来处理这些负载。
(nice=19)
5工作队列(workqueue)
1)工作队列可把工作推后,交由一个内核线程去执行。
2)工作队列在进程上下文执行,允许重新调度甚至是睡眠,但无法访问用户空间。
3)实现
工作者线程events/n,n是处理器编号。
worker_thread()函数:
执行一个死循环并休眠,当有操作,线程被唤醒。
4)使用工作队列
(1)声明
静态创建
DECLARE_WORK(name,void(*func)(void*),void*data)
动态创建
INIT_WORK(structwork_struct*work,void(*func)(void*),void*data)
(2)处理函数
voidwork_handle(void*data)
(3)调度
shedule_work(&work)马上调度
shedule_delayed_work(&work,delay)延时调度
(4)刷新
flush_scheduled_work(void)
等待队列中所有对象都被执行后才返回。
cancel_delayed_work(structwork_struct*work)
取消延时的工作。
(5)创建新的工作列队
可在默认的工作列队外创建新的进程:
creat_work(constchar*name)
queue_work()类似schedule_work(),针对自己的进程。
queue_delayed_work()
flush_workqueue()
6下半部机制的选择
下半部上下文顺序执行要求
软中断中断没有
tasklet中断同类型不可以同时执行
工作队列进程没有(和进程上下文一样被调度)
1)易用性:
工作列队>tasklet>软中断
2)速度:
软中断>tasklet>工作队列
3)开销:
工作队列>>tasklet、软中断
7下半部的锁机制
1)tasklet
(1)相同类型的tasklet不允许同时执行,无需同步。
(2)不同类型的tasklet需使用锁机制。
2)软中断所有共享数据结构都需要合适的锁。
3)进程上下文和下半部共享中断,上下文访问共享数据前,需禁止下半部并获取锁的使用权。
4)中断上下文和下半部共享中断,需禁止中断并取得锁使用权。
local_bh_disable()禁止本地处理器的软中断和tasklet处理,不用禁止工作列队。
local_bh_enable()
Linux内核设计与实现读书笔记
(2)8-10
第八章内核同步介绍
1临界区和竞争条件
临界区:
访问和操作共享数据的代码段。
竞争条件:
两个执行线程处于同一个临界区中。
2内核中造成并发的原因
1)中断:
任何时刻异步发生,打断当前执行的代码。
2)软中断和tasklet:
任何时刻唤醒或调度软中断、tasklet。
3)内核抢占(preempt)。
4)睡眠及与用户空间的同步:
唤醒调度程序,调度新进程执行。
5)对称多处理器(SMP)。
3需要加速的代码
1)中断安全代码;
2)SMP安全代码;
3)抢占安全代码。
★给数据加锁而不是给代码加锁。
4编程需注意的问题:
1)数据是否全局?
除了当前线程,其他线程是否可以访问?
2)数据是否在进程/中断上下文中共享?
是否在两个不同中断中共享?
3)进程在访问数据时可否被抢占?
被调度的新进程是否会访问同一数据?
4)当前进程是否会睡眠(阻塞)在某些资源上?
共享数据处于何种状态?
5)怎样防止数据失控?
若在另一处理器上调度?
5预防死锁
1)★加锁顺序是关键,使用嵌套锁必须以相同顺序获取锁;
2)防止发生饥饿;
3)不要重复请求同一个锁;
4)复杂的加锁方案也可能造成死锁——简化设计;
5)建议以获取锁相反的顺序来释放锁。
第九章内核同步方法
1原子操作
原子操作执行过程不被打断,原子操作接口分为整数操作接口和单独位操作接口。
2原子整数操作
只有atomic_t类型可用于整数原子操作。
atomic_t类型保证编译器不对相应的值进行优化。
atomic_t类型可以屏蔽不同体系结构上实现原子类型的差异。
atomic_t类型只能使用24位(现在已经没有这个限制)。
定义:
asm/atomic.h
atomic_tv;
atomic_tu=ATOMIC_INIT(0);
操作:
atomic_set(&v,4)
atomic_add(z,&v)
atomic_sub(inti,atomic*v)
atomic_inc(&v)
atomic_dec(&v)
atomic_read(&v)转成整形
atomic_dec_and_test(&v)给定原子变量减1,若为0则返回真。
3原子位操作
原子位操作对普通的内存地址操作,没有atomic_t类型。
定义:
asm/bitops.h
操作:
set_bit(intnr,void*addr)
clear_bit(intnr,void*addr)
change_bit(intnr,void*addr)翻转
test_and_set_bit()设置并返回原先的值
test_and_clear_bit()
test_and_change()
test_bit()返回第nr位
非原子操作:
__test_bit()
4自旋锁
自旋锁最多只能被一个可执行线程持有。
若一个线程试图获得一个被征用的自旋锁,线程会一直忙循环——选择——等待锁可用。
缺点:
由于自旋锁在等待时自旋(浪费处理器时间),因此自旋锁不应长时间持有。
优点:
线程不用睡眠,不用进行上下文切换。
数据结构:
结构相关代码:
asm/spinlock.h
接口定义:
linux/spinlock.h
声明:
spinlock_tmr_lock=SPIN_LOCK_UNLOCKED
加锁:
spin_lock(&mr_lock)
解锁:
spin_unlock(&mr_lock)
自旋锁特点:
自旋锁最多被一个线程持有,为SMP提供了并发保护机制。
单处理器编程时并不会加入自旋锁,仅当做检测内核抢占的开关。
如果禁止内核抢占,则自旋锁无效。
自旋锁可用于中断处理程序中(信号量不可用于自旋锁,会自旋),中断中使用自旋锁首先应当关中断。
自旋锁不可递归。
自旋锁操作:
spin_lock_irqsave(&mr_lock,flag)保存中断当前状态,关中断,获取锁
spin_unlock_irqstore(&mr_lock,flag)恢复中断到加锁前状态并释放锁
spin_lock_irq(&mr_lock)关中断,获取锁
spin_unlock_irq(&mr_lock)关中断,释放锁
因为很难搞清楚中断情况,推荐使用前者。
其他操作:
spin_lock_init()动态初始化知道spinlock_t
spin_trylock()试图获取指定锁,若未获取则返回非0
spin_is_locked()如制定锁当前正在被获取,则返回非0
4读写自旋锁
读写不同锁,多人和可并发持有读者锁,写锁只能被一个任务持有。
用法:
rwlock_tmr_rwlock=RW_LOCK_UNLOCKED;
read_lock(&mr_rwlock)
read_unlock(&mr_rwlock)
write_lock(&mr_rwlock)
write_unlock(&mr_rwlock)
注意:
(1)不可将读锁“升级”为写锁,写锁会等待读锁。
(2)多个读者可以安全获得同一个读锁,也可递归获取读锁。
5信号量
1)信号量的特点
(1)Linux中的信号量是一种睡眠锁;
(2)信号量适用于锁会被长期占有的情况;
(3)由于信号量会睡眠,所以只能用于进程上下文中,中断上下文不支持调度;
(4)可以在持有信号量时睡眠;
(5)不可以在持有信号量时使用自旋锁;
(6)信号量允许任意数目的锁持有者,自旋锁一个时刻只允许一个任务持有;
(7)在声明时课指定信号量拥有的持有者数量。
2)创建信号量
定义:
asm/semaphore.h
静态声明:
staticDECLARE_SEMAPHORE_GENEIC(none,count)
互斥信号量声明:
staticDECLARE_MUTEX(name)
动态创建:
sema_init(sem,count)
动态创建互斥对象:
init_MUTEX(sem)(注意大小写)
3)使用信号量
down()信号计数减1,若为0或大于0,则获取信号量,若为负数,则任务等待。
up()信号计数加1,若等待队列不为空,唤醒等待任务。
down_interruptible(structsemaphore*)若信号量已被征用,则可进入中断睡眠状态。
down_trylock()试图获取信号量,若已被征用,则立刻返回非零值。
6读-写信号量(都是互斥信号量)
定义:
linux/rwsem.h
静态声明:
staticDECLARE_RWSEM(name)
动态创建:
init_rwsem(structrw_semaphore*sem)
功能:
down_read()
up_read()
down_write()
up_write()
downgrade_writer()动态将写锁转换为读锁
7自旋锁与信号量
中断上下文中只能使用自旋锁。
任务睡眠时只能使用信号量。
需求建议
低开销加锁优先使用自旋锁
短期锁定优先使用自旋锁
长期加锁优先使用信号量
中断上下文加锁使用自旋锁
持有锁需要睡眠使用信号量
8完成变量
完成变量是同步两个任务的一种简单方法。
定义:
linux/completion.h
静态创建:
DECLARE_COMPLETION(mr_comp)
动态创建:
init_completion()
方法:
wait_for_completion(structcompletion*)等待指定的完成变量接受信号
complete(structcompletion*)发信号唤醒等待任务
9禁止抢占
内核代码使用自旋锁作为非抢占区域的标记。
preempt_disable()增加抢占计数值,从而禁止内核抢占
preempt_enable()减少抢占计数值,当减为0时检查和执行被挂起的任务
preempt_enable_no_resched()
preempt_count()返回抢占计数
get_cpu()
put_cpu()
10顺序和屏障
屏障(barrier):
确保城乡运行顺序的指令。
rmb()阻止跨越屏障的转入动作发生重排序。
read_barrier_depends()阻止跨越屏障的具有数据依赖关系的载入动作重排序。
wmb()阻止跨越屏障的载入和存储动作发生重排序。
mb()阻止跨越屏障的载入和存储动作发生重排序。
smp_rmb()在SMP上提供rmb()功能,在UP上提供barrier()功能。
smp_read_barrier_depends()
smp_wmb()
smp_mb()
barrirer()阻止编译器跨越屏障对载入或存储操作进行优化。
第十章定时器和时间管理
1时钟中断的工作
1)跟新系统运行时间;
2)跟新实际时间;
3)在SMP系统中均衡调度各处理器的运行列队;
4)检查当前进程是否耗尽时间片,若耗尽则进行重新调度;
5)运行超时的动态定时器;
6)更新资源消耗和处理器时间的统计值。
2节拍率HZ
ARM100(10ms)
i3861000(1ms)
x86-64
提高HZ的优点:
1)更高时钟中断解析度;
2)提高时间驱动事件的准确性(平均误差5ms->0.5ms);
3)内核定时器能够以更高的频度和更高准确度执行;
4)依
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux内核设计与实现 读书笔记 Linux 内核 设计 实现