[置顶] Linux内核设计与实现学习笔记

 

第一章 linux内核简介
	1.1 追寻linux的足迹:linux简介
	*起源于unix/minix,C,Internet + Linux Torvalds等开发者的努力
	*遵循GPL的非商业化发行
	*主要包括内核,C库,编译器,工具集,系统工具等

	1.2 操作系统和内核简介
	*内核需要实现中断处理,调度,内存管理,网络,IPC等功能
	*内核空间/用户空间
	*应用程序和内核通信:应用程序->C库->系统调用->内核
		+-----------+ +-----------+ +-----------+
		+ 应用程序1 + + 应用程序2 + + 应用程序3 +
		+-----------+ +-----------+ +-----------+
		     |		   |		  |
		     V		   V		  V
		+---------------------------------------+
		+		系统调用接口		+
		+---------------------------------------+
		     |		   |		  |
		     V		   V		  V
		+-----------------------+---------------+
		+	内核子系统	+		+
		+-----------------------+		+
		+		设备驱动程序		+
		+---------------------------------------+
		     |		   |		  |
		     V		   V		  V
		+---------------------------------------+
		+		硬件设备		+
		+---------------------------------------+
	*上下文
		-运行于内核空间,处于进程上下文,代表某个进程执行
		-运行于内核空间,处于中断上下文,处理某个特定中断
		-运行于用户空间,执行用户进程
	
	1.3 linux内核与传动unix内核比较
	*单内核(宏内核)/微内核
	*linux是单内核+模块/抢占/内核线程(PS:操作系统设计中的哲学思想,折衷)
	*linux特点:
		-动态加载模块
		-支持SMP
		-支持抢占
		-线程的实现方法
		-面向对象的设备模型
		-自由,摒弃unix中不好的东西,会不断吸取好的东西

	1.4 内核版本
	*版本命名规则
			aa.bb.cc
			|  |  |
	     主版本<----+  V  +---->修订版本
			 从版本(奇数为开发版,偶数为稳定版)

	1.5 社区
	*LKML:http://lkml.org/
	*linux kernel newbies:http://kernelnewbies.org/
	*linux kernel source:htt://www.kernel.org/

	1.6 小节
	*enjoy it


第二章 从内核出发
	2.1 获取源代码
		2.1.1 安装内核源代码
		$wget -c http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.24.tar.bz2
   		$tar xjf linux-2.6.24.tar.bz2
   		$cd linux-2.6.24
   		$make defconfig
   		$make all
   		$su -c "make modules_install install"
		摘自:http://kernelnewbies.org/KernelBuild
		原代码有两种压缩方式gzip和bzip2,bzip2压缩效果更好
		
		2.1.2 使用补丁
		For example's sake, you have unpacked a tarball of Linux 2.4.0, 		and you want to apply Linus' patch-2.4.1.bz2, which you have
		placed in /usr/src
       		$bzip2 -dc /usr/src/patch-2.4.1.bz2 | patch -p1 --dry-run
		摘自:http://kernelnewbies.org/FAQ/HowToApplyAPatch

	2.2 内核源代码树
		目录/文件	|	描述
		+-----------------------------------------------+
		+ arch		|	体系结构相关代码	+
		+ crypto	|	crypto API		+
		+ Doumentation	|	文档			+
		+ drivers	|	设备驱动程序		+
		+ fs		|	VFS和各种文件系统	+
		+ include	|	内核头文件		+
		+ init		|	内核引导和初始化	+
		+ ipc		|	进程间通信		+
		+ kernel	|	核心子系统		+
		+ lib		|	通用内核库函数		+
		+ mm		|	内存公里子系统和VM	+
		+ net		|	网络子系统		+
		+ scripts	|	编译内用脚本		+
		+ security	|	linux安全模块		+
		+ sound		|	语音子系统		+
		+ usr		|	早期用户空间代码(ramfs)	+
		+ COPYING	|	内核许可证(GPL)		+
		+ CREDITS	|	开发者列表		+
		+ MAINTAINERS	|	维护者列表		+
		+ Makefile	|	内核Makefile文件	+
		+-----------------------------------------------+

	2.3 编译内核
	*配置内核:make config,make menuconfig,make xconfig,make gconfig
	yes/no/module
	请参考:./readme
		http://kernelnewbies.org/KernelBuild
	减少输出垃圾信息:make > /dev/null
	衍生多个作业: make -j2,make -j4,...
	  
	2.4 内核开发的特点
	*没有C库
	*使用GNU C
	*没有用户空间的内存保护
	*不实用浮点数
	*内核栈空间有限
	*内核支持中断,抢占,SMP,因此需要注意同步和并发
	*需要考虑可移植性


第三章 进程管理
	进程/线程的概念
	虚拟内存(使进程觉得自己独占系统内存)/虚拟处理器(使进程觉得自己独享处理器)
	
	3.1 进程描述符及任务结构
	*任务队列:task_struct的双向循环链表
	*task_struct位于include/linux/sched.h

		3.1.1 分配进程描述符
		*通过slab分配器分配,达到着色缓存(cache coloring)的目的
		*寻址进程的task_struct
			+---------------+
			+	内	+
			+		+
			+	核	+
			+		+
			+	栈	+
			+---------------+
			+  thread_info	+
			+---------------+ -->task_struct

			内核栈的底端为thread_info,thread_info的第一个成员
			指向进程的task_sturct

		3.1.2 进程描述符的存放
		*PID:进程标识符,管理员可以修改/proc/sys/kernel/pid_max来改变
		系统中允许的进程数目
		*current宏
		请参考:http://kernelnewbies.org/FAQ/get_current

		3.1.3 进程状态
		*TASK_RUNNING
		*TASK_INTERRUPTIBLE
		*TASK_UNINTERRUPTIBLE
		*TASK_ZOMBIE
		*TASK_STOPPED
		*状态迁移图
								+---------------+
		fork创建任务					+ TASK_ZOMBIE	+
		   |						+---------------+
		   |							A
		   |		+-----------------------+	   任务	|
		   |任务创建	|    context_switch	|	   退出	|
		   |		|			V		|
		   |	+---------------+	+---------------+	|
		   +-->	+ TASK_RUNNING	+	+ TASK_RUNNING	+ ------+
		   +-->	+ 未占有CPU	+	+ 占有CPU	+ ------+
		   |	+---------------+	+---------------+	|
		   |		A			|		|
		   |		|	任务被抢占	|	   等待	|
		   |事件	+-----------------------+	   事件	|
		   |发生						|
		   |唤醒						|
		   |		+-----------------------+		|
		   +----------- + TASK_INTERRUPTIBLE	+ <-------------+
				+ TASK_UNINTERRUPTIBLE	+
				+-----------------------+

		3.1.4 设置当前进程状态
		*set_task_state(task, state)
		*set_current_state(state)
		
		3.1.5 进程上下文
		*内核代表进程执行
		*用户空间执行->系统调用/异常->陷入内核,此时处于进程上下文

		3.1.6 进程家族树
		*task_struct的parent,sibling,children域
		*task_sturct的tasks域
		*init进程
		*内核通用数据结构list,include/linux/list.h

	3.2 创建进程
	*fork() exec()
		3.2.1 写时拷贝
		*copy-on-write,只有在写入的时候,数据才被复制。

		3.2.2 fork()
		*fork()
			->sys_fork
				->do_fork()
					->...

		3.3.3 vfork()
		*vfork()
			->sys_vfork()
				->do_fork()
					->...
		*不推荐用vfork(),而使用fork()

	3.3 线程在linux中的实现
	*linux中线程是共享资源的进程
	*clone()共享资源的标志,include/linux/sched.h
	*内核线程,运行在内核空间,没有独立的地址空间,完成特殊的任务
	如,ksoftirqd,kswapd,pdflush

	3.4 进程终结
	*exit()
		->sys_exit
			->do_exit()
				->...
	*释放进程拥有的资源,但是此时没有释放task_struct

		3.4.1 删除进程描述符
		*wait()
			->wait4()
		*父进程执行wait4(),等待子进程退出,然后子进程的task_struct才被释放

		3.4.2 孤儿进程造成的进退维谷
		*如果父进程在子进程之前退出,需要为子进程寻找新的父进程
		do_exit()
			->notify_parent()
				->forget_origial_parent()

	3.5 进程小结
	*操作系统最重要的抽象:进程


第四章 进程调度
	*调度程序,选择哪个进程占有CPU,原则就是最大限度的利用CPU
	*抢占式多任务/非抢占是式多任务
	*linux采用了O(1)的调度算法

	4.1 策略
	*调度策略是在什么时候让哪个进程运行
		4.1.1 I/O消耗型和CPU消耗型进程
		*CPU消耗型,降低优先级,否则会影响系统对用户的响应
		*I/O消耗型,提高优先级,尽量满足用户的快速响应
		*而一个进程可能同时具有两种特性,有时候需要消耗大量的CPU时钟,
		而有时候又要等待用户介入进行一些操作,调度策略就是在这种矛盾中
		寻找平衡,这里有体现了哲学中的矛盾思想
	
		4.1.2 进程优先级
		*linux采用动态优先级调度,所谓动态,是指调度程序从等待I/O时间
		和占用处理器的时间推测,从而动态增减优先级(nice值)。
		*linux提供两组优先级,实时优先级和nice值。第一组,nice值,-20~+19。
		实时优先级,0~99。

		4.1.3 时间片
		*默认时间片的规定,太大,则系统交互性差,太小,明显会增加进程
		切换时处理器的消耗。这里有体现了哲学中的矛盾思想。
		*linux默认时间片为100ms。
			更低优先级		更高优先级
		|<----------------------|---------------------->|
		|	更低交互性	|	更高交互性	|
		V			V			V
		最小		       默认		      最大
		5ms		       100ms		      800ms

		4.1.4 进程抢占
		*当进程变为TASK_RUNNING的时候,内核检查优先级是否高于当前进程
		如果是则当前进程被抢占,进程时间片为0的时候,也会唤醒调度程序
		抢占当前进程

		4.1.5 调度策略的活动
		*为交互性强的进程分配更高的优先级,高优先级可以抢占低优先级,提高
		系统整体性能

	4.2 linux调度算法
	*kernel/sched.c
	*调度算法实现了目标
		-O(1)调度
		-SMP扩展性,每个处理器有自己的锁和运行队列
		-SMP亲和力
		-加强交互性
		-保证公平
		-可优化到多处理上,每个处理器有多个进程在运行(FIXME:不太理解这是什么意思)
		
		4.2.1 可执行队列
		*运行队列runqueuq,可执行进程的链表,每个处理器一个
		*cpu_rq宏,this_rq宏
		*运行队列锁,操作运行队列时候需要所锁定
		*task_rq_lock(),task_rq_unlock(),this_rq_lock(),this_rq_unlock
		
		4.2.2 优先级数组
		*活动数组,过期数组,prio_array
		*优先级数组是实现O(1)调度算法的核心
		struct prio_array{
			int		nr_active;		/* 任务数目 */
			unsigned long	bitmap[BITMAP_SIZE];	/* 优先级位图 */
			struct list_head queue[MAX_PRIO];	/* 优先级队列 */
		}
		sched_find_first_bit(),查找bitmap中的最高优先级。
		查找bitmap的结果作为queue的索引,取得最高优先级的task_struct。
		
		4.2.3 重新计算时间片
		*每次从活动数组移动到过去数组之前就计算好时间片。从而当活动数组中
		没有进程的时候,只需要切换活动数组和过期数组。
		if (!array->nr_active){
			rq->active = rq->expired;
			rq->expired = array;
		}

		4.2.4 schedule()
		*schedule()
			->context_switch()
				->switch_to()

		4.2.5 计算优先级和时间片
		*优先级计算:nice+effective_prio()
		*时间片:task_timeslice(),按照优先级进行缩放
		*交互很强的进程时间片用完之后,如果没有过期数组饥饿,
		可以被重新放如活动数组

		4.2.6 睡眠和唤醒
		*DECLARE_WAITQUEUE()创建等待队列
		add_wati_queue()加入等待队列
		设置task状态为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE
		如果设置了TASK_INTERRUPTIBLE,判断是否是收到信号的伪唤醒
		判断等待条件是否为真,如果是取消睡眠,否则调用schedule()
		当进程被唤醒后,检查是等待条件是否为真,如果是,退出循环,否则再次调用schedule(),一直重复这个动作
		等待条件满足后,设置task状态为TASK_RUNNING,并且调用remove_wait_queue()移出等待队列

		4.2.7 负载平衡
		*load_balance(),只有在SMP的时候才是有效的,负责在各个处理器之间平衡运行队列

	4.3 抢占和上下文
	*need_resched标志
	*内核在想要调度的时候,需要设这这个标志,如进程时间片耗尽(scheduler_tick),更高优先级变为TASK_RUNNING(try_to_wake_up),从内核空间返回到用户空间,从中断中返回的是时候,都需要检查need_resched标志

		4.3.1 用户抢占
		*从系统调用返回到用户空间,需要检查need_resched标志
		*从中断处理程序返回到用户空间,需要检查need_resched标志

		4.3.2 内核抢占
		*引入preempt_count标志内核是否持有锁,如果没有并且need_resched标志被设置,则可以发生内核抢占
		*从中断处理程序总返回到内核空间之前
		*当内核再一次具有可抢占的时候
		*显示调用schedule()
		*内核任务被阻塞

	4.4 实时
	*SCHED_FIFO
	*SCHED_RR
	*SCHED_NORMAL(通常进程)

	4.5 与调度相关的系统调用
		4.5.1 与调度策略和优先级相关的系统调用
		*sched_setscheduler(),sched_getscheduler()
		*sched_setpara(),sched_getpara()
		*sched_get_priority_max(),sched_set_priority_min()

		4.5.2 与处理器绑定有关的系统调用
		*sched_setaffinity()

		4.5.3 放弃处理器时间
		*sched_yield()

	4.6 调度程序小结
	*在各种矛盾中折衷


第五章 系统调用
	*提供用户空间和内核空间通信的一组接口
	*除了异常和陷入外,系统调用是用户空间程序进入内核的唯一合法入口

	5.1 API POSIX C库
	*提供机制而不是策略,设计是需要抽象出需要提供的功能,而至于外部如何使用这些功能则不用关心
	*用户空间程序和内核通信
	+---------------+	+-----------------------+	+---------------+
	+ 调用printf()	+ --->	+ C库中printf C库中write+ --->	+ write系统调用	+
	+---------------+       +-----------------------+       +---------------+
	    应用程序-------------------->C库------------------------->内核

	5.2 系统调用
	*名称为sys_bar()
	*所有的系统调用为asmlinkage的,从内核栈上取得参数
	*通常0表示成功,失败的时候可以通过peeror()取得全局的errno

		5.2.1 系统调用号
		*entry.S中系统调用函数表的位置

		5.2.2 系统调用性能
		*上现文切换高效,系统调用实现简洁

	5.3 系统调用处理程序
	*int 0x80产生异常,系统切换到内核态,执行异常处理程序
	*system_call()异常处理程序
		5.3.1 指定恰当的系统调用
		*系统调用号存放在EAX寄存器中

		5.3.2 参数传递
		*ebx,ecx,edx,esi,edi存放5个参数
		*返回指存放在eax

	5.4 系统调用的实现
	*良好的设计是关键,简洁,通用,可移植
	*参数验证,内核需要对系统调用作严格的检查,特别是用户空间的指针
		-copy_from_user()可以睡眠
		-copy_to_user()可以睡眠
		-suser()检查用户是否具有超级用户权限
		-capable()更细粒度的检查权限

	5.5 系统调用上下文
	*系统调用上下文属于进程上下文,可以睡眠可抢占
	*系统调用需要可重入
		5.5.1 绑定一个系统调用的最后步骤
		*在系统调用表最后追加一个表项
		*定义系统调用号,位于asm/unistd.h文件
		*定义系统调用函数,任何文件都可,但是不能编译为模块

		5.5.2 从用户空间访问系统调用
		*long open(const char * filename, int flags, int mode)
		*#define NR_open	5
		_syscall3(long, open, const char*, filename, int, flags, int, mode)
		最新的版本已经不支持

		5.5.3 为什么不通过系统调用的方式实现
		*虽然linux中,追加系统调用非常容易,但是不提倡用这种方式,因为需要
		系统调用号,稳定后的内核就被固化了,需用追加到各种体系结构中等

	5.6 系统调用小结
	*API 系统调用 陷入内核 系统调用号,参数传递...


第六章 中断和中断处理程序
	*轮询(polling)
	*中断(interrupt

	6.1 中断
	*中断是外设产生的经PIC送到CPU的异步中断信号
	*异常是CPU内部执行指令出现错误或者特殊情况时产生的同步中断信号

	6.2 中断处理程序
	*中断处理程序interrupt handler(ISR)
	*内核调用来响应外部设备,需要响应快速,并且需要尽快完成
	*上半部于下半部(top half and bottom half)

	6.3 注册中断处理程序
	*/kernel/irq/manage.c
	 int request_irq(unsigned int irq, irq_handler_t handler,
                unsigned long irqflags, const char *devname, void *dev_id)
	Flags:
	IRQF_SHARED             Interrupt is shared
	IRQF_DISABLED   	Disable local interrupts while processing
	IRQF_SAMPLE_RANDOM      The interrupt can be used for entropy
	dev_id用于区分同一跟中断线上的共享中断
	此函数可能会引起睡眠,不能在中断上下文和不允许阻塞的代码中使用
	释放中断:void free_irq(unsigned int irq, void *dev_id)

	6.4 编写中断处理程序
	*即是编写request_irq()中的handler函数
	  linux中断处理程序无须可重入,因为一个中断在处理的时候,这个中断在所有处理器上都是被屏蔽的
	6.4.1 共享的中断处理程序
	*request_irq()中的flag必须是SA_SHIRQ,dev_id必须唯一,需要硬件设备支持和中断处理程序中判断逻辑
	6.4.2 中断处理程序实例
	*请参考drivers/char/rtc.c

	6.5 中断上下文
	*执行中断处理程序或者下半部,称为中断上下文,中断处理程序不能睡眠,有严格的执行时间要求,尽量少的内存使用

	6.6 中断处理程序的实现
	*设备产生中断->经总线到中断控制器->没有屏蔽送到CPU->中断内核->do_IRQ->有中断处理程序->handle_irq_event->运行该线上的所有中断处理程序->ret_from_intr->被中断的代码
	/proc/interrupts,porcfs文件系统是虚拟文件系统,存在于内核内存,安装在/proc目录下,代码位于fs/proc目录下

	6.7 中断控制
	6.7.1 禁止和激活中断
	*禁止和激活当前处理器上的本地中断
	  local_irq_disable(),local_irq_enable()
	*禁止之前保存中断状态,激活后恢复中断状态
	  unsigned long flags;
	  local_irq_save(flags);
	  local_irq_restore(flags);
	6.7.2 禁止指定中断线
	*disable_irq()/enable_irq()
	  disable_irq_nosync()/synchornize_irq()
	  不能禁止共享中断线,disable和enable次数一致

	6.7.3 中断系统的状态
	*中断禁止还是激活:irqs_disable(),禁止返回0
	  内核处于中断上下文:in_interrupt()
	  内核在执行中断处理程序:in_irq()
	
	6.8 别打断我,马上结束
	*中断打断了其它代码,需要快速执行完成,但是又有很多工作要作,所以内核提供了下半部机制(bottom half),在下一章讨论


第七章 下半部和推后执行的工作
	7.1 下半部
	*执行和中断相关的但是中断处理程序不执行的那部分工作
	  中断处理程序需要完成对硬件的应答,数据的拷贝,硬件的控制等,其余可留给下半部执行
	  划分上半部(中断处理程序)和下半部的基本原则:时间敏感,硬件相关,不能被中断打断,要放在中断处理程序中,其余部分都可放在下半部中
	7.1.1 为什么要用下半部
	*关这中断时间过长会影响系统的相应能力,下半部仅仅是将任务推迟一点,通常在中断处理程序返回就会执行,下半部在开中断的条件下执行
	7.1.2 下半部的环境
	*实现下半部有不同的方法,老的BH,任务队列,灵活性和性能都不理想,已被摒弃
	  2.6提供了三种下半部的实现方法:软中断(softirqs),tasklet和工作队列
	  另外,内核定时器可以把任务推后到固定的时间执行

	7.2 软中断
	7.2.1 软中断的实现
	*代码位于kernel/softirq.c
	  static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
	  include/linux/interrupt.h
	  struct softirq_action
	  { 
        	void    (*action)(struct softirq_action *);
        	void    *data;
	  };
	  软中断处理程序:mysoftirq->action(mysoftirq)
	  执行软中断:从硬件中断代码中返回,从ksoftirqd内核线程,内核中显式检查执行待处理的软中断
	  do_softirq()
	  
	7.2.2 使用软中断
	*软中断只保留给系统中对时间要求最为严格的子系统使用,目前为SCSI和网络子系统
	  tasklet和内核定时器都是建立在软中断之上
	  分配索引:位于include/linux/interrupt.h中的枚举
	  注册处理程序: open_softirq()
	  触发软中断:raise_softirq()

	7.3 tasklet
	*利用软中断实现的下半部机制
	7.3.1 tasklet的实现
	*include/linux/interrupt.h
	struct tasklet_struct
	{
        	struct tasklet_struct *next;
        	unsigned long state;
	        atomic_t count;
	        void (*func)(unsigned long);
        	unsigned long data;
	};

你可能感兴趣的:(linux,struct,action,任务,makefile,linux内核)