linux内核札记

来源:互联网 发布:手机好莱坞特效软件 编辑:程序博客网 时间:2024/05/16 07:13

1)内核的主要组成:进程调度,内存管理,虚拟文件系统,网络接口,进程间通信.
进程调度(SCHED):控制进程对CPU的访问。当需要选择下一个进程运行时,由调度程序选择最值得运行的进程.

内存管理单元(MMU):允许多个进程安全的共享主内存区域.

虚拟文件系统(VFS):隐藏了各种硬件的具体细节,为所有的设备提供了统一的接口

网络接口(NET):提供了对各种网络标准的存取和各种网络硬件的支持

进程间通信(IPC):支持进程间各种通信机制

1.make xxxxx_defconfig:选取某一具体型号的IC的默认配置到.config文件
2.make menuconfig :启动图形化内核配置界面
3.make uImage:生成uboot专用的内核文件

uImage:是在zImage的基础上,在原本文件的开头,加上一个长64字节的头,说明这个内核的版本,加载位置,生成时间,大小等信息得到。

4.make modules:用来生成独立的模块:*.ko

模块操作相关函数:
modprobe:加载内核模块时,如果有依赖的模块,同时加载所依赖的模块.也可以卸载模块
lsmod:显示系统中正在运行的模块,
insmod:加载模块到内核,不检测是否有依赖模块,如果有依赖模块,则加载不成功
rmmod:删除一个正在运行的模块
modinfo: 查看内核模块包含的模块信息

模块参数:加载模块时,可传入参数给模块.
module_param(参数名,参数类型,读写权限);
在安装模块时这样使用 : insmod xx.ko 模块参数 =传入值
模块导出符号:导出函数或者变量
EXPORT_SYMBOL(xxxx)
EXPORT_SYMBOL_GPL(XXX)
当某个模块想要导出自己本身的符号给其他模块用时,使用上面两个函数导出符号。
当某个模块想要使用另一个模块的导出符号时,使用 extern xxxxx 来声明下.

5.linux中内存最小管理单位为页(page),一个page一般为4k.初始化时,linux为每个物理内存页建立一个page结构,操作物理内存实际上就是操作page页.

虚拟地址:32位cpu 2^32:0x0-0xFFFF FFFF(4G).

进程空间的划分:
用户空间:0G-3G(size:3G range:0x-0xBFFF FFFF)
内核空间:3G-4G(size:1G range:0xc000 0000 -0xFFFF FFFF)
通常用户进程只能访问进程空间中的用户空间,不能访问内核空间。例外:系统调用时可以访问到内核空间
用户空间的内存分配:
malloc /free
valloc/free

内核空间的内存分配:
kmalloc/kfree:分配的内存物理上连续
kzalloc/kfree: 自动初始化内存空间为0,其他同上
get_zeroed_page/free_page:分配一个页,同初始化为0,
__get_free_pages/ free_pages:分配指定页数的内存
alloc_pages / __free_pages:分配指定页数的内存
vmalloc/vfree:分配一块内存,物理上不需要连续
内核分配标志:
GFP_KERNEL:正常申请内核内存,允许睡眠,
GFP_ATOMIC:不允许睡眠
__GFP_DMA:用于分配DMA内存
__GFP_HIGHMEM:分配的内存可位于高端内存
6.内核定时器

HZ常数:内核定时器中断发生的频率,若HZ=200,代表每 1/200 s发生一次中断,每次发生中断的间隔为5ms,这个时间间隔被称作tick.

jiffies:被用来记录自开机以来,经过了多少个tick.

struct timer_list {    /*     * All fields that change during normal runtime grouped to the     * same cacheline     */    struct list_head entry;    unsigned long expires;    struct tvec_base *base;    void (*function)(unsigned long);    unsigned long data;    int slack;#ifdef CONFIG_TIMER_STATS    int start_pid;    void *start_site;    char start_comm[16];#endif#ifdef CONFIG_LOCKDEP    struct lockdep_map lockdep_map;#endif};

内核定时器的初始化:
1)静态初始化: TIMER_INITIALIZER(_function,_expires,_data)
_function:定时器超时处理函数
_expires:定时器超时jiffies 时间
_data:传递给超时处理函数的参数
2)定义一个定时器:
DEFINE_TIMER(_name,_function,_expires,_data)
_name:待定义内核定时器变量名
3)动态初始化:
init_timer(struct timer_list *timer)
timer:带初始化的内核定时器
4)添加定时器:
add_timer(struct timer_list * timer)
5)删除定时器:
void del_timer(struct timer_list * timer)
6)修改定时器:
mod_timer(struct timer_list * timer,unsigned long expires)
该函数分3个步骤:1,del_timer(),timer->expires=expires,add_timer()
7.进程
1)内核线程:
进程描述符,PID,进程正文段,核心堆栈
2)用户进程:
进程描述符,PID,进程正文段,核心堆栈,用户空间的数据段和堆栈
3)用户线程:
进程描述符,PID,进程正文段,核心堆栈,同父进程共享用户空间的数据段和堆栈

4)进程四要素:
@1.有独立的用户空间
@2.进程有进程私有的内核空间堆栈
@3.进程在内核中有一个代表他的task_struct的结构,即通常所说的进程控制块,内核通过这个数据结构来组织和调度进程.
@4.进程有一段代码供其执行,这段代码可以非当前进程私有,可以是与其他进程共享的代码片段.

5)task_struct结构:
pid_t pid:进程号,最大为32768
volatile long state:进程状态
int exit_state:进程退出状态
struct mm_struct *mm:进程用户空间描述指针,对于内核线程来说,为空
unsigned int policy:进程调度策略
int piro:进程优先级
int static_piro:静态优先级
struct sched_rt_entity rt:进程调度实体
struct task_struct *real_parent:真实的父进程
char comm[TASK_COMMON_LEN]:不包含路径的执行程序的名称
6)进程状态:
TASK_RUNNING:正在被执行,或者已经就绪随时可执行,正在被创建的进程处于此阶段
TASK_INTERRUPTIBLE:处于等待中的进程,当等待条件为真时被唤醒,也可以被信号或者中断唤醒
TASK_UNINTERRUPTIBLE:处于等待中的进程,待资源有效时被唤醒,但不可以由其他进程通过信号或者中断唤醒
TASK_KIALLABLE:可以被致命信号SIGKILL唤醒
TASK_STOPED:进程暂时中止执行,当接收到SIGSTOP和SIGTSTP信号时,进程进入该状态,接收到SIGCONT信号后,进程重新回到TASK_RUNNING状态
TASK_TRACED:正处于被调试的进程
TASK_DEAD:进程用do_exit()退出后,进程处于该状态.
7)进程退出状态
EXIT_ZOMBIE:僵死状态,表示进程的执行被终止,父进程还没有发送wait_pid()系统调用来搜集有关死亡进程的相关信息
EXIT_DEAD:僵死撤销状态,表示进程的最终状态,父进程已经通过wait_pid()或者wait4()搜集了信息,因此进程将被系统删除.
8)进程优先级:
取值范围:0-139(数字越小,优先级越高)
0-99:实时进程
MAX_RT_PRIO -MAX_PRIO-1:非实时进程
优先级常量:

#define MAX_USER_RT_PRIO    100#define MAX_RT_PRIO     MAX_USER_RT_PRIO#define MAX_PRIO        (MAX_RT_PRIO + 40)#define DEFAULT_PRIO        (MAX_RT_PRIO + 20)

8)进程静态优先级:数字越小,优先级越高
作用:决定进程初始时时间片的大小
无论实时进程还是非实时进程都一样,只不过实时进程的该值不参与优先级运算
9)时间片
rt_time_slice:表示进程运行占用的时间片
进程缺省时间片与进程静态优先级有关
内核将100-139优先级的进程映射到200-10ms的时间段上,数值越大,分配的时间片越小
10)current指针:
该指针总是指向当前正在运行的task_struct结构体
当进程被创建,为进程申请task_struct空间时(大约1k),系统将连同系统的堆栈空间一起分配,分配该空间时以8KB为单位分配
11)进程调度:
从准备好的进程中选择一个合适的进程来执行
调度策略:
SCHED_NORMAL:传统的调度策略,适用于交互式的分时应用
SCHED_FIFO:先来的先执行,适用于对时间性要求比较强,且执行时间比较短的进程,实时应用程序适合该策略
SCHED_RR(round robin):轮流的意思,适合程序比较大,每次运行都要花很长世间的进程
SCHED_BATCH:适用于具有batch风格(批处理),CPU密集型进程,非交互型,该进程不抢占另外的进程
SHCED_IDLE:适用于优先级比较低的进程,该进程仅在系统空闲时才会被调用
12)调度时机
主动式:
@1.在内核中,当进程需要等待资源而暂时停止运行时,通过调用schedule()或者schedule_timeout()自愿放弃cpu,调度其他进程,调度前应该将进程状态置为挂起
@2.在用户空间,通过系统调用pause(),nanosleep()而自愿放弃cpu.
被动式:被抢占
可以是内核抢占,也可以是用户抢占
@1.内核抢占:更高优先级的进程/线程可以抢占当前正在运行时的低优先级的进程/线程
@2.用户抢占:当内核即将返回用户空间的时候,如果 need_resched()测试TIF_NEED_RESCHED标志被设置,会导致schedule()被调用,此时发生的抢占成为用户抢占
中断处理,系统调用,异常处理返回用户空间都算
13)不允许内核抢占的特例:
@1.内核正进行中断处理
@2:内核正在进行中断上下文的底半部处理.
@3:进程正持有自旋锁,读写锁等情况.
@4: 内核正在执行调度程序.
14)内核抢占计数器:
为保证在以上情况不被抢占,linux内核使用了一个变量preempt_count,称为内核抢占计数,这个变量被设置在进程的thread_info结构中,每当进程进入到以上几种状态时,preempt_count便+1,表示内核不允许抢占,每当内核出现以上几种状态的退出时,preempt_count便-1,表示允许重新调度与抢占

15)调度步骤:
清理当前运行中的进程
选择下一个要运行的进程
设置新进程的运行环境
进程上下文切换
系统调用:
由操作系统内核实现,运行在内核态
普通调用:由函数库或者用户自己提供,运行在用户态
8并发与竟态
1)中断屏蔽:local_irq_disable(), local_irq_enable()都只能禁止/使能本CPU内的中断,对于SMP(多核),不可行
而且,linux系统的异步IO,任务调度等重要操作都依赖于中断,所以采用中断屏蔽来避免竟态并不可取
local_irq_save,不仅可以屏蔽中断,同时可以保存cpu的中断位信息。local_irq_restore与它相反
如果只是想禁止中断的底半部,那么则local_bh_disable(),locla_bh_enable()。
2)原子操作:指的是在执行过程中不会被别的代码路径所中断的操作.分为整形原子操作和位原子操作.
a.整形原子的操作
1.atomic_set(atomic_t *v,int i):设置原子变量的值为i。
2.atomic_t v =ATOMINC_INIT(0):定义原子变量v,并初始化为 0.
3.atomic_read(atomic_t *v):返回原子变量的值
4.void atomic_add(int i,atomic_t *v):原子变量+i
5.void atomic_sub(int i,atomic_t *v):原子变量-i
6.void atomic_inc(atomic_t *v):原子变量+1
7.void atomic_dec(atomic_t *v):原子变量-1
8.int atomic_inc_and_test(atomic_t *v):自增1,并测试是否为0,为0返回true
9.int atomic_dec_and_test(atomic_t *v):自减1,并测试是否为0,为0返回true
10.int atomic_sub_and_tets(int i,atomic_t *v):减去i,并测试是否为0,为0返回true
11.int atomic_add_and_return(int i,atomic_t *v):原子变量+i,并返回新的值
12.int atomic_sub_and_return(int i,atomic_t *v):原子变量-i,并返回新的值
13.int atomic_inc_return(atomic_t *v):自增1并返回新的值
14.int atomic_dec_return(atomic_t *v):自减1并返回新的值
b.位原子操作
1.void set_bit(nr,void *addr):设置addr地址的nr位,置1
2.void clear_bit(nr,void *addr):清除addr地址的nr位,置0
3.void changebit(nr,void *addr):对addr地址的nr位取反
4.test_bit(nr,void *addr):返回addr的nr位
5.int test_and_set_bit(nr,void *addr):测试并设置nr位
6.int test_and_clear_bit(nr,void *addr):测试并清除nr位
7.int test_and_change_bit(nr,void *addr):测试并对nr位取反

3)自旋锁
1.spin lock:如果锁未被占用,则获得这个锁并执行程序。如果锁被占用,则原地自旋等待,直到其他程序释放锁后。
2.自旋锁的操作:
a.spinlock_t lock:定义自旋锁
b.spin_lock_init(lock):初始化自旋锁
c.spin_lock(lock):获得锁,如果被占用,原地自旋等待
d.spin_trylock(lock):获得锁,如果被占用,返回假。不原地自旋等待
e.spin_unlock(lock):释放锁,和spin_lock()/spin_try_lock()配对使用
f.使用范例:

spin_lock_t lock;spin_lock_init(&lock);spin_lock(&lock);//do 临界区资源代码spin_unlocked(&lock);

自旋锁持有期间,内核的抢占将被禁止。自旋锁能够保证临界区资源不受别的CPU和本CPU内的抢占进程打扰。
但得到锁的代码路径在执行临界区的时候还可能受到中断断和底半部的影响.则会有自旋锁的衍生锁
4)读写锁:是一种比自旋锁粒度更小的锁,保留了自旋的概念,在写操作时最多只允许一个写进程。在读操作方面,允许同时多个进程读。
读写锁的操作:
a.rwlock_t lock =RW_LOCK_LOCKED//静态初始化
b.rwlock_t lock; rwlock_init(&lock);//动态初始化
读锁定:
c.read_ lock(rwlock_t *lock);
d.read_ lock_irq(rwlock_t *lock);
e.read_ lock_irqsave(rwlock_t *lock,unsigned long flags);
f.read_ lock_bh(rwlock_t *lock);
读解锁:
g.read_unlock(rwlock_t *lock);
h.read_unlock_irq(rwlock_t *lock);
i.read_unlock_irqrestore(rwlock_t *lock,unsigned long flags);
j.read_unlock_bh(rwlock_t *lock);
写锁定
k.write_lock(rwlock_t *lock);
l.write_lock_irq(rwlock_t *lock);
m.write_lock_irqsave(rwlock_t *lock,unsigned long flags);
n.write_lock_bh(rwlock_t *lock);
o.write_trylock(rwlock_t* lock);
写解锁:
p.write_unlock(rwlock_t *lock);
q.write_unlock_irq(rwlock_t *lock);
r.write_unlock_irqrestore(rwlock_t *lock,unsigned long flags);
s.write_unlock_bh(rwlock_t *lock);

5)顺序锁:是一种对读写锁的优化,若使用顺序锁,读操作绝对不会被写执行单元阻塞,仅仅写操作是互斥的.
限制:要求被保护的临界资源不含指针
操作:
a. seqlock_t lock;
b.seqlock_init(&lock);
c.unsigned read_seqbegin(seqlock_t *lock);//读顺序锁
d.int read_seqretry(seqlock_t *lock,unsigned start)//读检查
e.write_seqlock(seqlock_t *lock);
f.write_tryseqlock(seqlock_t *lock);
g.write_seqlock_irq(seqlock_t *lock);
h.write_seqlock_irqsave(seqlock_t *lock,unsigned long flags);
i.write_seqlock_bh(seqlock_t *lock)

j.write_sequnlock(seqlock_t *lock)
k.write_sequnlock_irq(seqlock_t *lock);
l.write_sequnlock_irqrestore(seqlock_t *lock,unsigned long flags);
m.write_sequnlock_bh(seqlock_t *lock)
6)RCU 读拷贝更新…..

7)信号量(semaphore):当获取不到信号量时,进程不会原地打转自旋,而是进入休眠等待
信号量的操作:
a.struct semaphore sem;
b.sema_init(struct semaphore *sem,int val):初始化信号量,并赋值为val
c.init_MUTEX(struct semaphore *sem):初始化为1
d.init_MUTEX_LOCKED(struct semaphore *sem):初始化为0,初始化后便处于锁定状态
e.DECLARE_MUTEX(name):定义一个名为name的信号量,并初始化为1
f.void down(struct semaphore *sem):获取信号量,可能导致睡眠,不能在中断上下文使用
g.int down_interruptible(struct semaphore *sem):获取信号量,如果信号量不可用,进程被设为TASK_INTERRUPTIABLE类型的睡眠状态,返回0代表信号获取正常返回,非0代表信号被中断.
h.int down_killable(struct semaphore *sem):获取信号量失败会进入TASK_KILLABLE的睡眠状态
i.int down_trylock(struct semaphore *sem):尝试获取,获取成功,立即返回。否则返回非0,不会睡眠,可以在中断上下文使用.
j.void up(struct semaphore *sem):释放信号量,实际上是给信号量+1.

DECLARE_MUTEX(sem);down(&sem);//do 临界资源up(&sem);

信号量被初始化为0,可用于同步,同步意味着一个执行单元继续执行需等待另一个执行单元完成某事,保证执行的先后顺序
8)完成量:
struct completion com;
init_completion(&com);
DECLARE_COMPLETION(com):定义并初始化完成量
void wait_for_completion(struct completion *com);等待完成量
void complete(struct completion *c)
void complete_all(struct completion *c )
前者释放一个等待的执行单元,后者释放所有等待同一完成量的执行单元
9)互斥体
10)等待队列可用于阻塞进程的唤醒
定义和初始化队列头:
wait_queue_head_t wqh;
init_waitqueue_head(wait_queue_head_t *wqh);
DECLARE_WAITQUEUE(name.tsk):定义和初始化等待队列
add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait):将等待队列添加到等待队列头所在的列表中
remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait):将等待队列从等待队列头所在的列表中删除
等待事件:
wait_event(queue,condition):condition为真时,立即返回,否则进入TASK_UNINTERRUPTIBLE 的睡眠状态,并挂在queue指定的等待队列头上.
add_wait_queue(queue,condition):条件为真,立即返回,否则进入TASK_INTERRUPTIBLE的睡眠状态,并挂在queue指定的等待队列头上
wait_event_killable(wq,condition):同上,否则进入TASK_KILLABLE类型的睡眠状态
wait_event_timeout(wq,condition,timeout):同上,否则进入TASK_UNINTERRUPTIBLE类型的睡眠状态,并挂在指定wq的等待对猎头上,当超时后,立即返回
wait_event_interruptible_timeout(wq,condition,timeout):…
唤醒:
wake_up(x)
wake_up_interruptible(x)
在等待队列中睡眠:
sleep_on(wait_queue_head_t * q)
interruptible_sleep_on(wait_queue_head_t * q)

阻塞/非阻塞:如果设备没有准备好数据给应用程序读,或者没有准备好接收应用程序的写数据,那么设备应当阻塞进程,使他进入睡眠,直到请求看可以得到相应.
用户应用程序可以使用O_NONBLOCK标志来人为地设置读写方式为非阻塞

这里写图片描述
select系统调用:用于多路监控,当没有一个文件满足要求时,select调用将引起系统阻塞
unsigned int (poll) (struct file , struct poll_table_struct *);第一个参数:文件表指针,第二个参数:轮询表指针
使用 poll_wait()函数将等待队列添加到轮询表中.

/* * Do not touch the structure directly, use the access functions * poll_does_not_wait() and poll_requested_events() instead. */typedef struct poll_table_struct {    poll_queue_proc _qproc;    unsigned long _key;} poll_table;

poll_wait(struct file * filp,wait_queue_head_t * wait_address,poll_table * p):poll_wait()的调用不会引起阻塞,它仅仅将当前进程添加到wait参数指定的等待队列的数据链中
poll操作的返回值:
POLLIN:设备可无阻塞读
POLLOUT:设备可无阻塞写
POLLRDNORM:数据可读
POLLWRNORM:数据可写
11)异步通知
阻塞:设备未就绪则阻塞进程
非阻塞:设备未就绪则直接返回状态
轮询:由应用程序查询状态判断设备是否就绪
异步:一旦设备就绪,就主动通知应用程序.
异步通知使用信号来实现,除SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他所有信号
实现:
用户应用程序:
1.完成F_SETOWN cmd:设备设备的拥有者为本进程。这样才能接收到设备驱动发出的信号
2.完成F_SETFL cmd:该命令设置文件支持FASYNC模式,几异步通知模式
3.完成signal()系统调用:该系统调用连接SIGIO信号和对应的信号处理函数
驱动程序实现:
1.支持F_SETOWN 控制命令:在控制命令中需要将filp->f_owner设为对应进程的ID,这部分由内核完成.
2.支持F_SETFL:每当FASYNC中的标志位改变时,设备驱动中的fasync()得以执行,所以驱动中需要实现fasync()函数
3.释放信号:在设备驱动中,当设备资源可以获得时,设备驱动应该通过调用kill_fasync()函数释放相应的信号.
异步事件通知队列:

struct fasync_struct {    spinlock_t      fa_lock;    int         magic;    int         fa_fd;    struct fasync_struct    *fa_next; /* singly linked list */    struct file     *fa_file;    struct rcu_head     fa_rcu;};int (*fasync) (int fd, struct file *filp, int on);//返回:+值表示成功,0表示未变,负值失败

fasync_helper(int fd,struct file * filp,int on,struct fasync_struct * * fapp):
初始化fasync_struct异步事件通知队列,包括分配内存和设置属性
释放初始化时为fasync_struct分配的内存.
kill_fasync(struct fasync_struct * * fp,int sig,int band):
在设备资源可以获得时,应调用该函数释放SIGIO信号

异步事件的发起者问题:
当进程收到SIGIO信号时,没办法区分是那个文件有新数据提供,此时仍需要select()函数来找出是那个文件发生变化

0 0
原创粉丝点击