/*
author:C雄
内容是关于linux2.6的
本文档是驱动基础相关的资料,不涉及具体硬件驱动(如LED、按键、等)。
2013.3.26
*/
头文件大全:
{
1. include/linux/list.h 精彩的双向链表数据结构
2. linux/timer.h timer定时器
asm/uaccess.h jiffies
3. asm/semaphore.h 信号量
4. linux/device.h bus_type等
}
时钟和定时器:
{
时钟相关概念:{
时钟脉冲:一个按一定电压幅度,一定时间间隔发出的脉冲信号。
时钟频率:在单位时间(如1秒)内产生的时钟脉冲的个数。
}
时钟作用:{
时钟信号是时序逻辑的基础,它用于决定逻辑单元中的状态何时更新。数字芯片中众多的晶体管都工作在开关状态,它们的导通和关断动作无不是按照时钟信号的节奏进行的。
}
时钟产生:{
时钟产生-晶振:
晶体振荡器,是石英经过精密切割做成的。振幅很稳定,结构简单,噪声低,但是定制的晶振成本高,难制作。
时钟产生-PLL(锁相环):
PLL(锁相环)合成器是一种更为复杂的系统时钟源。通过PLL合成器需要一个外部晶体并包含一个能够对晶体的特定频率加倍或分频的继承锁相环(PLL)电路。
晶振和锁相环的区别:
典型的振荡器采用晶振,更复杂的系统振动器由PLL合成器提供。对于特定的时钟频率,采用PLL合成器可以使用较便宜以及较低频率的晶振来代替昂贵的高频晶振。对于需要多个时钟频率的系统可以用PLL合成器经过分频实现,而不是用多个晶振代替。相对晶振模块,PLL合成器提供精确时钟具有成本低、占板面积更小的一系列优点。
}
s3c6410:{
s3c6410有三个PLL:
APLL 生成一个独立ARM 操作时钟。
MPLL 生成系统参考时钟。
EPLL 产生用作外设IP 的时钟。
}
s3c2440的频率相关设置:{
1. 刚上电FCLK(内核信号频率) = 外部晶振频率。
2. 在设置MPLL的几个寄存器后,一段时间(长度由LOCKTIME寄存器控制)进入Lock Time,MPLL的输出才稳定,而这段时间FCLK停振来设置FCLK,CPU停止工作。
3. 在这之后FCLK以更高的频率运行。
三个相关寄存器:LOCKTIME(6410和2440不同)、MPLLCON(6410没有)、CLKDIVN(6410没有)。LOCKTIME设置locktime时间,MPLLCON设置FCLK和Fin的倍数,CLKDIVN设置FCLK和HCLK和PCLK三者的比例。
}
}
中断处理函数:
{
为什么需要中断:(1)外设的处理速度一般慢于CPU(2)CPU不能一直等待外部事件总是轮询或查询,必须有一种方法通知CPU发生了某事或汇报工作进度。
1.注册中断(常常在open中实现)
int request_irq(unsigned int irq ,void (*handler)(int,void *,struct pt_regs *) ,unsigned long flags ,const char *devname ,void *dev_id)//返回0表示成功,或者返回错误码
相关形参:
unsigned irq中断号
void (*handle) (int ,void * ,struct pt_regs)中断处理函数
unsigned long flags与中断管理相关的各种选项(例如上升沿,双岩):
IRQF_DISABLED(2.4: SA_INTERRUPT)表示快速中断,如果没设置为慢速中断【快速中断不可被打断并在同时关闭所有中断!】
IRQF_SHARED (2.4: SA_SHIRQ)【共享设备必须指定】表示这个中断可以在同一中断信号线设备间共享(主要是linux为了支持PCI设备服务)
const char *devname属于哪个设备!!
void *dev_id共享中断时使用,如果则irq表示共享中断的总线,dev_id表示具体中断设备(共享时必须唯一)
2.释放中断(常在关闭卸载中实现)
当设备不需要使用中断时,通常在驱动程序卸载时将它们返还给系统。
void free_irq(unsigned int irq,void *dev_id)/*释放分配给已定中断的内存*/
disable_irq(unsigned int irq)/*使定义中断链失效,如果使用这个函数,同一中断信号线的所有设备都不能使用这个中断函数*/
3.中断处理程序
禁忌:1.向用户空间发送数据或接收数据2.不可以使用可能引起阻塞的函数(wakeup等)3.不能使用可能引起调度的函数。(这些对应中断上下文,不属于进程上下文,不能传输数据;另外,对于中断函数中调用可能阻塞或调度的函数,轻则引起系统任务调度打乱,失去实时性,重则系统崩溃或复位。)
中断函数处理流程:
void short_sh_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
//判断是否本设备产生了中断(为什么要这么检测)
value = inb(short_base); //short_base只是个驱动写手的中断标志位
of(!(value &0x80)) return;
/*原因是对于共享中断线的设备,内核只知道具体中断线的号码,然后调用所有设备的中断处理程序,就像这个程序,当判断不是自己这个设备产生中断的时候就会return*/
//清除中断位,对于read之后或write之后不会自动清除的设备,重新局部初始化设备
outb(value & 0x7f,short_base);
//中断处理核心,通常是数据接收
...................
//唤醒等待数据的进程
wake_up_interrupt(&short_queue);
}
}
进程调用和系统调用和proc和系统异常分析
{
进程{
进程进入内核空间的方法: 1. 系统调用,如open 2. 中断
进程四要素{
1.有一段程序供其运行。这段程序不一定是某个进程所专有,可以与其它进程公用。
2.有进程专用的内核空间堆栈。
3.在内核中有一个task_struct数据结构,即通常所说的“进程控制块”。有了这个数据结构,进程才能成为内核调度的一个基本单位。
4.有独立的用户空间。(有独立的用户空间?【是:进程;】 【否:有用户空间吗?【是:用户线程;】【否:内核线程。】】)
}
进程描述{
进程系统空间包含堆栈和task_struct的指针( 2.6 )或堆栈和task_struct( 2.4 )
在linux中,线程、进程都使用struct task_struct来表示,它包含了大量描述进程/线程(无论内核还是用户)的信息,其中比较重要的有:
pid_t pid; //进线程号,最大值10亿
struct mm_struct *mm 进程用户空间描述指针,内核线程该指针为空
unsigned int policy 该进程的调度策略
int prio 优先级,数字大不优先
int static_prio{
静态优先级,与2.4 的nice值意义相同。nice值仍沿用linux的传统,在-20 到19 之间变动,数值越大,进程的优先级越小。nice是用户可维护的,但仅影响非实时进程的优先级。进程初始化时间片的大小仅决定于进程的静态优先级,这一点无论是实时进程还是非实时进程都一样,不过实时进程的static_prio不参与优先级计算。nice与static_prio的关系如下:
static_prio = MAX_RT_PRIO + nice + 20;
内核定义了两个宏完成这一转换:PRIO_TO_NICE()、NICE_TO_PRIO()。
另外内核将100- 139 的优先级映射到200ms-10ms 的时间片上,优先级越大,分配时间越短。
}
不属于struct task_struct的变量,current指向正在运行的进程的task_struct。
}
进程状态{
volatile long state; //进程状态
1.TASK_RUNNING 正在被执行或就绪随时可以被执行(进程刚被创建就处于这个状态)
2.TASK_INTERRUPTIBLE
可中断阻塞,处于等待中的进程,当等待条件为真时被唤醒,也可以被信号或者中断唤醒
3.TASK_UNINTERRUPTERIBLE 处于等待中的进程,带资源有效时唤醒,但不可以由其他进程通过信号(SIGNAL)或中断唤醒。
4.TASK_STOPPED 进程终止执行。当接收到SIGSTOP和SIGTSTP等信号时,进程进入该状态,接收到SIGCONT信号后,进程重新回到TASK_RUNNING
5.TASK_KILLABLE linux2.6.25新引入的进程睡眠状态,原理类似于TASK_UNINTERRUPTIBLE,但是可以被致命信号(SIGKILL)唤醒杀死。
6.TASK_TRACED 正处于进程调试状态的进程
7.TASK_DEAD 进程退出时(调用do_exit),state字段被设置为该状态。
int exit_state;//进程退出时候的状态
1.EXIT_ZOMBIE 僵死状态,表示进程执行被终止,但是父进程还没有发布waitpid()系统调用来收集有关死亡的进程的信息。
2.EXIT_DEAD 僵死撤销状态(资源撤销),表示进程的最终状态,父进程已经使用wait4()或者waitpid()系统调用来收集了信息,因此进程将由系统删除。
进程状态切换见图片。
}
进程销毁{
内核空间:fatal signals <-> do_exit() <-> sys_exit() <-> 用户空间exit()
}
}
进程调度{
概念:什么是调度? 从就绪的进程中选出最实惠的一个来执行。
学习的知识点:1.调度策略2.调度时机3.调度步骤
调度策略{
SCHED_NORMAL(SCHED_OTHER):普通的分时进程
SCHED_FIFO:先入先出的实时进程
SCHED_RR:时间片轮转的实时进程
SCHED_BATCH:批处理进程
SCHED_IDLE:只在系统空闲时才能被调度执行的进程
调度类:
struct sched_class { /* Defined in 2.6.23:/usr/include/linux/sched.h */
struct sched_class *next;
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int wakeup);
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int sleep);
void (*yield_task) (struct rq *rq, struct task_struct *p);
void (*check_preempt_curr) (struct rq *rq, struct task_struct *p);
struct task_struct * (*pick_next_task) (struct rq *rq);//下一个要执行的进程
void (*put_prev_task) (struct rq *rq, struct task_struct *p);
unsigned long (*load_balance) (struct rq *this_rq, int this_cpu,
struct rq *busiest,
unsigned long max_nr_move, unsigned long max_load_move,
struct sched_domain *sd, enum cpu_idle_type idle,
int *all_pinned, int *this_best_prio);
void (*set_curr_task) (struct rq *rq);
void (*task_tick) (struct rq *rq, struct task_struct *p);
void (*task_new) (struct rq *rq, struct task_struct *p);
};
//调度类的引入增加了内核调度程序的可拓展性,这些类(调度程序模块)封装了调度策略,并将调度策略模块化。
CFS调度类(在kernel/sched_fair.c中实现)用于以下调度策略:SCHED_NORMAL(比较公平的)、SCHED_BATCH和SCHED_IDLE。
实时调度类(在kernel/sched_rt.c中实现)用于SCHED_RR和SCHED_FIFO策略。
}
调度时机{//调度什么时候发生,即:schedule()后汉书什么时候被调用
1.主动式
在内核中直接调用schedule()。当进程需要等待资源或暂时停止运行时,会把状态挂起(睡眠),并主动请求调度,让出CPU。
主动放弃CPU实例:current->state = TASK_INTERRUPTIBLE;
schedule();
2.被动式(抢占):【用户抢占(linux2.4、2.6)内核抢占(linux2.6)】
a.用户抢占:发生在内核空间返回用户空间的时候:1.从系统调用返回用户空间2.从中断处理程序返回用户空间。【内核即将返回用户空间的时候,如果need_resched标志被设置,会导致schedule()被调用,此时就会发生用户抢占】
b.内核抢占:{
在不支持内核抢占的系统中,进程/线程一旦运行于内核空间,就可以一直执行,直到它主动放弃或时间片耗完为止。这样其他一些非常紧急的进程或线程将长时间得不到运行。 在支持内核抢占的系统中,更高优先级的进程/线程可以抢占正在内核空间运行的低优先级的进程/线程。
不允许内核抢占的情况{
1.正在进行中断处理,schedule判断并打印错误
2.处于中断上下文
3.正持有自旋锁spinlock、 writelock /readlock读写锁。(若抢占可能会无法解锁)
4.正在执行调度程序
}
内核抢占说明{
为保证linux内核在以上情况下不会被抢占,抢占式内核使用了一个变量preempt_count(== 0 则可抢占),成为内核抢占计数。这一变量被设置在进程的thread_info结构中。每当内核要进入以上几种状态时,变量preempt_count + 1 , 指示内核不允许抢占。每当内核从以上几种状态退出时,preempt_count - 1 ,同时进行可抢占的判断和调度。
}
调度标志:TIF_NEED_RESCHED内核提供了need_resched标志来表明是否需要重新执行一次调度。
设置这个标志的时机:1.当时间片耗完2.一个更高优先级的进程进入可执行状态的时候
}
}
调度步骤{
schedule函数工作流程如下:1)清理当前运行中的进程2)选择下一个要运行的进程pick_next_task分析3)设置新进程的运行环境4)进程上下文切换
}
}
}
混杂设备驱动程序://主设备号为10,次设备号不同的一类设备,所有混杂设备形成一个链表,对设备访问的时候根据次设备号查找到相应的miscdevice设备
{
struct miscdevice//描述一个混杂设备
{
int minor;//次设备号
const char *name;//设备名
const struct file_operations *ops;//操作函数
struct list_head list;
..........
}
int misc_register(struct miscdevice *misc)//只要对一个初始好的结构使用这个函数即可。
void misc_deregister(struct miscdevice *misc)
}
sysfs文件系统和kobject、kset:
{
简介{
a ram-based filesystem,showed the linkages data structures and atttribute between kernel and userspace
sysfs把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问到。
详细作用:在这里注册能够找到同一设备的多种对应关系(总线,驱动,设备),但是并不涉及他们的读写。更多的是建立他们之间的关系和可阅读的属性,例如:匹配设备和驱动的并probe、_creat_file创建属性,platform建立直接的对应关系、继承父设备(.parent = &my_bus,)等
}
子目录{
.Block:在系统中发现的每个块设备在该目录下对应一个子目录。每个子目录中又包含一些属性文件,它们描述了这个块设备的各方面属性,如:设备大小。(loop块设备是使用文件来模拟的)
.Bus:在内核中注册的每条总线在该目录下对应一个子目录,如:
ide pci scsi usb pcmcia
其中每个总线又包含两个子目录:devices和drivers,devices目录包含了整个系统中发现的属于该总线类型的设备,drivers目录包含了注册到该总线的所有驱动。
.Class:将设备按照功能进行分类,如/sys/class/net目录下包含了所有网络接口。
.Devices:包含系统所有的设备。
.kernel:内核中的配置参数。
.Module:系统中所有模块的信息。
.Firmware:系统中的固件
.FS:描述系统中的文件系统
.Power:系统中的电源选项
}
kobject和kset{
kobject{
kobject实现了面向对象的管理机制(相当于父类,基类),是构成linux2.6 设备模型的核心结构。它与sysfs文件系统紧密相连,在内核中注册的每个kobject对象对应sysfs文件系统中的一个目录。(kobject将devices,drivers连接起来,形成了一个树状结构。)
struct kobject{
const char *name;
struct list_head entry;
struct kobject *parents;//指向父对象
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kerf kref; //队形引用计数
.................
}
操作:
void kobject_init(struct kobject *kobj)//初始化kobject结构
int kobject_add(struct kobject *kobj);//将kobject对象注册到linux系统中
int kobject_init_and_add(struct kobject *kobj,struct kobj_type *ktype,struct kobject *parent,const char *fmt,...)//初始化kobject,并将其注册到linux系统
void kobject_del(struct kobject *kobj)//从linux系统中删除kobject对象
struct kobject *kobject_get(struct kobject *kobj)//将kobject对象的引用计数+1,同时返回该对象指针。
void kobject_out(struct kobject *kobj)//将kobject对象的引用计数-1,如果音悦台计数将为0,则调用release方法释放该kobject对象。
struct kobj_type{
void (*release) (struct kobject *kobj);//用于事项kobject占用的资源,当kobject的引用计数为0时候调用。
struct sysfs_ops *sysfs_ops;
struct attribute ** default_attrs;//指向指针的指针,可以指向多个attrs结构,对应多个属性文件
}
struct attribute{
char *name; //属性文件名
struct module *owner;
mode_t mode; //属性保护位
};
struct attribute(属性):在这部分一个attribute对应于kobject的目录下的一个文件,Name成员就是文件名。
struct sysfs_ops{
ssize_t (*show)(struct kobject *,struct attribute *,char *);//需要用户自己添加
ssize_t (*store)(struct kobject *,struct attribute *,const char *,size_t);//需要用户自己添加
}
.show:当用户读属性文件时,该函数被调用,该函数将属性值存入buffer中返回给用户态。
.store:当用户写属性文件时,该函数被调用,用于存储用户传入的属性值。
}
kset{
kobject是只能包含文件的目录,而kset是能包含kobject的目录!
kset是具有相同类型的kobject的集合,在sysfs中体现成一个目录,定义为:
struct kset{
struct list head_list; //连接该kset中所有kobject的链表头
spinlock_t list_lock;
struct kobject kobj; //内嵌的kobject
struct kset_uevent_ops *uevent_ops;//处理热插拔时间的操作集合
操作:
int kset_register(struct kset *kset)//在内核中注册一个kset
void kset_unregister(struct kset *kset)//从内核中注销一个kset
热插拔事件{
在linux系统中,当系统配置发生变化时,如:添加kset到系统、移动kobject,一个通知会从内核空间发送到用户空间,这就是热插拔事件。热插拔事件会导致用户空间中相对应的处理程序(如udev,mdev)被调用,这些处理程序会通过加载驱动程序,创建设备节点来相应热插拔事件。
}
struct kset_uevent_ops{
int (*filter) (struct kset *kset,struct kobject *kobj);//决定是否将时间传递到用户空间。如果filter返回0,将不传递事件。
const char *(*name) (struct kset *kset,struct kobject *kobject);//用于将字符串传递给用户空间的热插拔处理程序。
int (*uevent)(struct kset *kset,struct kobject *kobj,struct kobj_uevent_env *edv);//将用户空间需要的参数添加到环境变量中
};
这三个函数指针处理,但这三个函数什么时候被调用呢????
当该kset所管理的kobject和kset本身状态发生变化时(如被加入,移动),这三个函数将被调用(例如:kobject_uevent调用)。
}
}
}
}
总线、设备、驱动模型和platform: //并未完全搞懂
{
总线{
定义:处理器和设备之间的通道,在设备模型中,所有设备都通过总线相连,甚至是内部虚拟的"platform"总线。
struct bus_type{
const char *name; //名字
struct bus_attribute *bus_attrs; //总线属性
struct device_attribute *dev_attrs; //设备属性
struct driver_attribute *drv_attrs; //驱动属性
int(*match)(struct device *dev,struct device_driver *drv)
int(*uevent)(struct device *dev,char **envp,int num_envp,char *buffer,int buffer_size)
.............各种函数指针!!
}
总线属性:
struct bus_attribute{
struct attribute attr;
ssize_t (*show) (struct bus_type *,char *buf);//读取设备文件
ssize_t (*store) (struct bus_type *,char *buf,size_t count);//写设备文件
}
注册和删除
bus_register(struct bus_type *bus)//如果成功,新总线会被添加,并可以在sysfs的/sys/bus看到
void bus_unregister(struct bus_type *bus)
总线方法
int(*match)(struct device *dev,struct device_driver *drv)//当一个新设备或者驱动被添加到这个总线时,该方法被调用。用于判断指定的驱动程序是否能处理指定的设备。若可以返回非零值。
int(*uevent)(struct device *dev,char **envp,int num_envp,char *buffer,int buffer_size)//在未用户控件产生热插拔事件之前,这个方法允许总线添加环境变量
int bus_create_file(struct bus_type *bus,struct bus_attribute *attr)//创建属性
void bus_remove_file(struct bus_type *bus,struct bus_attribute *attr)//删除属性
}
设备{
struct device{
...........
struct kobject kobj;
char bus_id[BUS_ID_SIZE];//在总线上唯一标示该设备的字符串
struct bus_type *bus; //设备所在总线
struct device_driver *driver;//管理该设备的驱动
void *driver_data; //该设备驱动使用的私有数据成员
................
}
设备属性
struct device_attribute
{
struct attribute attr;
ssize_t (*show)(struct device *dev,struct device_attribute *attr,char *buf);
ssize_t (*store)(struct device *dev,struct device_attribute *attr,const char *buf,size_t count);
}
int device_register(struct *dev)//注册设备
void device_unregister(struct device *dev)//注销设备,一条总线也是个设备,也必须按设备注册
int device_create_file(struct device *devi,struct device_attribute *entry);//创建属性
void device_remove_file(struct device *dev,struct device_attribute *attr);//删除属性
}
驱动{
struct device_driver{
const char *name; //驱动程序的名字
struct bus_type *bus; //驱动程序所在总线
struct module *owner;
const char *mod_name;
int (*probe)(struct device *dev);//当驱动找到它匹配的设备的时候就会调用它
................
}
驱动属性
struct driver_attribute{
struct attribute attr;
ssize_t (*show) (struct device_driver *drv,char *buf);
ssize_t (*store) (struct device_driver *drv,const char *buf,size_t count);
}
int driver_register(struct device_driver *drv)//注册驱动
void driver_unregister(struct device_driver *drv)//注销驱动
int driver_create_file(struct device_driver *drv,struct driver_attribute *attr)//创建属性
void driver_remove_file(struct device_driver *drv,struct driver_attribute *attr)//删除属性
}
platform{
platform总线是linux2.6 加入的虚拟总线,它并不复杂,由platform_device 和 platform_driver组成。
platform驱动与传统设备驱动模型相比,优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时使用统一的接口,这样提高了程序的可移植性。
流程:定义platform_device、注册platform_device、定义platform_driver、注册platform_driver
设备
struct platform_device{
const char *name; //设备名
int id; //设备编号,配合设备名使用
struct device dev;
u32 num_resources;
struct resource *resource;//设备资源:中断号,基地址等
}
平台设备资源例子:
static struct resource s3c_wdt_resource1 = {
.start = 0x44100000,
.end = 0x44200000,
.flags =
}
平台设备分配:
struct platform_device *platform_device_alloc(const char *name ,int id)//name设备名,id设备id(一般为-1)
平台设备注册:
int platform_device_add(struct platform_device *pdev)
}
}
并发和竞态:
{
并发:多个执行单元同时被执行。竞态:并发的执行单元对共享资源(硬件资源和软件上的非局部变量等)的访问导致的竞争状态。
{//例子
if(copy_from_user(&(dev->data[pos]) ,buf , count))//将buf到data,如果有两个进程或线程在进行这个操作,数据将变得混乱。假如是单核,也可能混乱(例如一个进程被挂起,另一个进程同时执行这个内容)
ret = -EFAULT;
goto out;
}
处理并分的常用技术是加锁或者互斥,即确保在任何时间只有一个执行单元可以共享资源。
信号量可以有多个持有者(除了互斥信号量),而自旋锁自由一个持有者。信号量时候长时间的,自旋锁时候适合时间很短,常常只有很少行代码的情况。
信号量:
{
信号量在概念和原理上与用户态的信号量是一样的,但是它不能再内核之外使用,它是一种睡眠锁(获取不成功就会发生睡眠或阻塞)。
struct semaphore类型用来表示信号量。
1.定义信号量
struct semaphore sem;
2.初始化信号量
void sema_init(struct semaphore *sem,int val)//该函数用于初始化信号量的初值,它设置信号量sem的值为val
void init_MUTEX(struct semaphore *sem)//该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1
void init_MUTEX_LOCKED(struct semaphore *sem)//该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就在已锁状态
{
DECLARE_MUTEX(name) //定义一个信号量name,并初始化为1
DECLARE_MUTEX_LOCKED(name)//定义一个信号量name,并初始化为0,即创建的时候已锁
}
3.获取信号量
void down(struct semaphore *sem)//获取信号量sem,可能会导致进程睡眠(不宜在中断上下文使用)。该函数将sem减1,如果信号量sem非负,直接返回,否者调用者挂起,知道别的任务释放该信号才能继续运行。不推荐使用。
int down_interruptible(struct semaphore *sem)获取信号量,如果进程将被设置为TASK_INTERRUPTIBLE类型的睡眠状态。该函数由返回值区分是正常返回还是被信号中断返回,如果返回0,表示获得信号量正常返回,如果信号被打断,返回-EINTR。
down_killable(struct semaphore *sem)//获取信号量sem。如果信号量不可用,进程将被置为TASK_KILLABLE类型的睡眠状态。
4.释放信号量
void up(struct semaphore *sem)//该函数释放信号量sem,即把sem的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒这些等待进程。
}
自旋锁:
{
自旋锁最多只能被一个可执行单元持有,自旋锁不会引起调用者睡眠,如果一个执行线程试图获得一个已经被持有的自旋锁,那么线程就会一直忙循环,直到自旋锁的保持着释放了锁。
spin_lock_init(x) 该宏用于初始化自旋锁x,自旋锁在使用前必须先初始化。
spin_lock(lock) 获取自旋锁lock,如果成功,立刻解锁,马上返回。否则一直自旋,直到自旋锁的保持着释放了锁。
spin_trylock(lock) 试图获取自旋锁,如果立刻获得锁返回真,否则返回假,不等待。
spin_unlock(lock) 释放自旋锁lock,它与spin_trylock或spin_lock配对使用。
}
}
数据结构的算法:
{
struct list_head
{
struct list_head *next,*prev;
}//list_head结构包含两个指向list_head结构的指针,组织成双向链表
初始化链表头:
INIT_LIST_HEAD(list_head *head)
插入节点:
list_add(struct list_head *new,struct list_head *head)
list_add_tail(struct list_head *new,struct list_head *head)
删除节点:
list_del(struct list_head *entry)
从个list_head中提取数据结构:
list_entry(ptr,type,number)//ptr指向某个list_head变量,是节点指针,type指向要操作的结构体(包含上述list_head变量的结构体),list_head在结构中对应的变量名
list_entry(aup,struct autofs,list)//返回需要的包含数据结构体的指针
遍历:
list_for_each(struct list_head *pos,struct list_head *head)//本质是个for语句的宏
#define list_for_each(pos,head) \
for(pos = (head) - >next;prefetch(pos->next),pos != (head);\
pos = pos -> next)
{//例如:
struct list_head *entry;
struct list_head cs45xx_devs;//链表头
list_for_each(entry,&cs46xx_devs)
{
card = list_entry(entry,struct cs_card,list);//list是list_head在其中对应的结构名
if(card -> dev_midi == minor)
break;
}
}
}
内核定时器://只执行一次,并不循环执行
{
struct timer_list{
struct list_head entry;//内核使用,用函数需要初始化
unsigned long expires;//超时的jiffies值,自己设置,不提供具体函数
void (*function) (unsigned long);//超时的处理函数,自己设置,不提供具体函数
unsigned long data;//超时处理函数参数,自己设置,不提供具体函数
struct tvec_base *base;//内核使用,用函数需要初始化
}
void init_timer(struct time_list *timer)//初始化两个变量
void add_timer(struct timer_list *timer)//启动定时器
int del_timer(struct timer_list *timer)//在定时器超时前将它删除,当定时器超时后,系统会自动删除
}
等待队列:{
在linux驱动程序设计中,可以使用等待队列来实现进程的阻塞,等待队列可以看作保存进程的容器,在阻塞进程时,江金城放入等待队列,当唤醒进程时从等待队列中取出进程。
方法:{
1.定义等待队列
wait_queue_head_t my_queue
2.初始化等待队列
init_waitqueue_head(&my_queue)
3.定义并初始化等待队列
DECLARE_WAIT_QUEUE_HEAD(my_queue)
4.有条件睡眠
wait_event(queue,condition)
直到condition(一个布尔值表达式)为真,才会唤醒队列,立刻返回;否则让进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数指定的等待队列上。(不可中断)
wait_event_interruptible(queue,condition)
直到condition(一个布尔值表达式)为真,才会唤醒队列,立刻返回;否则让进程进入TASK_INTERRUPTIBLE模式的睡眠,并挂在queue参数指定的等待队列上。(可中断)
wait_event_killable(queue,condition)
直到condition(一个布尔值表达式)为真,才会唤醒队列,立刻返回;否则让进程进入TASK_KILLABLE模式的睡眠,并挂在queue参数指定的等待队列上。(可杀死,推荐)
5.无条件睡眠(老版本,不推荐)
sleep_on(wait_queue_head_t *q)
让进程进入不可中断的睡眠,并把它放入等待队列q。
interruptible_sleep_on(wait_queue_head_t *q)
让进程进入可中断的睡眠,并把它放入等待队列q。
6.从等待队列唤醒进程
wake_up(wait_queue_t *q) //是唤醒queue这个队列所有的进程哦
从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE的所有进程。
wake_up_interruptible(wait_queue_t *q)
从等待队列q中唤醒状态为TASK_INTERRUPTIBLE的所有进程。
}
功能:设备无法立刻满足用户的读写请求,如read不到数据,或写数据而设备没准备好。驱动程序应该(缺省地)阻塞进程,使它进入睡眠。
读写阻塞{
阻塞方式:在阻塞型驱动程序中,read实现方式如下:如果进程调用read,但没有数据或数据不足,进程阻塞。当进程到达后唤醒进程。
非阻塞方式:阻塞方式是文件读写操作的默认方式,但是应用程序员可通过使用 O_NONBLOCK 标志来认为地设置读写操作为非阻塞方式。标志定义在<linux/fcntl.h>,在打开文件时候指定。 如果设置了O_NONBLOCK标志,read和write的行为是不同的。如果进程在没有数据就调用read或缓冲区没有空间就调用wtite,系统只会简单返回-EAGAIN,而不会阻塞。
}
}
小写宏或函数大全(很不全):
{
1.内存:{
get_zeroed_page(unsigned int flags)//会清零
__get_free_page(unsigned int flags)
__get_free_pages(unsigned int flags,unsigned int order)
flags:
GFP_KERNEL 进程上下文中的分配,可以睡眠。
GFP_ATONIC 用于进程上下文之外的代码(例如中断处理)中分配内存,不会导致睡眠,如果错误就返回错误值。
__GFP_DMA 获取DMA的内存区(用于DMA传输,16M以下的地址)
__GFP_HIGHMEM 获取高端内存(896M以上)
void free_page(unsigned long addr)
void free_pages(unsigned long addr,unsigned long order)//若释放和分配时候不等的页面,会导致系统错误
void *kmalloc(size_t size,int flags)
void kfree(const void *ptr)
}
2.申请设备(号)和设备文件和注册设备{
静态申请int register_chrdev_region(dev_t from,unsigned count,const char *name)//申请从from开始的count个设备号,设备名为name,体现在/proc/devices
动态申请int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)//请求内核动态分配count个设备号,且设备号从baseminor开始,设备名为name,体现在/proc/devices。dev用来获取分配到的设备号的地址
void unregister_chrdev_region(dev_t from,unsigned count)
3.创建设备文件:
devfs_register(devfs_handle_t dir,const char *name,unsigned int flags,unsigned int major,unsigned int minor,umode_t mode,void *ops,void *info)
//2.4内核版本在指定的目录中串见设备文件。dir:目录名,为空则在/dev/下创建。name:文件名;flags:创建标志;major:主设备号;minor次设备号;mode:创建模式;ops:操作函数集;info:通常为空。
{//2.6版本创建设备文件方法的使用
struct class *myclass = class_create(THIS_MODULE,"my_device_driver");
device_create(myclass,NULL,MKDEV(major_num,0),NULL,"my_device");//这样当驱动被加载就会自动在/dev下创建my_device设备文件
}
4.注册设备(分配,初始化,添加,注销):
struct *cdev *cdev_alloc(void);//分配设备注册号
void cdev_init(struct cdev *cdev,const struct file_operations *fops) //cdev待初始化的cdev结构,fops设备对应的操作函数集
int cdev_add(struct cdev *p,dev_t dev,unsigned count)//p:待添加到内核的字符设备结构,dev设备号,count添加的设备个数
int cdev_del(struct cdev *p)//p:要注销的字符设备结构
}
5.设备操作函数{
开关函数:
int (*open) (struct inode *,struct file *)//不要求一定要实现这个方法,若在file_operations中为NULL,永远打开成功
void (*release) (struct inode *,struct file *)//和open一样,也可用没有
读写定位函数:
ssize_t xxx_read(struct file *filp,char __user *buff,size_t count,loff_t *offp) //从设备读取数据
ssize_t xxx_write(struct file *filp,char __user *buff,size_t count,loff_t *offp) //向设备发送数据
//filp是文件指针;buff映射到用户空间的数据数据缓存,用户可修改;count是是请求的传输数据量,offp是文件偏移位置
off_t (*llseek) (struct file *,loff_t ,int )//修改文件当前的读写位置,并将新位置作为返回值。
监控:
应用层
『int select (int maxfd,fd_set *readfds,fd_set * writefds,fe_set *exceptfds,const struct timeval *timeout)』/*select系统调用用于多路监控,当没有一个文件满足要求时,select将可中断阻塞调用进程。maxfd文件描述符的范围,比待检测的最大文件描述符大1;readfds被读监控的文件描述符集;writefds被写监控的文件描述符集;exceptfds被异常监控的文件描述符集;Timeout定时器(让这个系统调用有不同效果)
Timeout为0,立刻返回,无文件返回0,有文件返回一个正值
Timeout为NULL,select将阻塞进程,知道某个文件满足要求
Timeout为正整数,select在Timeout时间内阻塞进程。
返回值:
1. 正常情况下返回满足要求的文件描述符个数。
2. 经过了Timeout等待仍无文件满足要求,返回0
3. 被某个信号中断,将返回-1,并设置errno为EINTR
4. 如果出错,返回-1并设置相应的errno
select使用方法:
1.将要监控的文件添加大盘文件描述符(见大写宏部分)
2.调用select开始监控
3.判断文件是否发生变化
『使用方法:
FD_ZETO(&fds);//清空集合
FD_SET(fd1,&fds);//设置描述符
FD_SET(fd2,&fds);//设置描述符
maxfdp = fd1 +1 //描述法最大值+1,假设fd1>fd2
switch(select (maxfdp , &fds ,NULL,NULL,&timeout))//读监控文件
{
case -1:exit(-1);break;
case 0:break;
default:
if(FD_ISSET(fd1.&fds))//测试fd1是否可读
}
』*/
驱动层
unsigned int (*poll) (struct file *filp,struct poll_table_struct *wait)//对应select系统调用
负责完成:1.使用poll_wait将等待队列添加到poll_table中 2.返回描述设备是否可读或可写的掩码(poll并不产生阻塞,select可能会产生阻塞)
位掩码:POLLIN:设备可读;POLLRDNORM数据可读;POLLOUT设备可写,POLLWRNORM数据可写
设备可读通常那个返回(POLLIN | POLLRDNORM)
设备可写通常那个返回(POLLOUT | POLLWRNORM)
{ //范例:
static unsigned int mem_poll(struct file *filp,poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = 0;
//把进程添加到等待队列
poll_wait(filp,&dev_inq,wait);
//返回掩码
if(有数据可读)
mask = POLLIN | POLLRDNORM;//设备可读
return mask;
}
}
控制:
int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)/*控制设备,cmd从用户空间传下来,arg以unsigned long传递(当不需要参数的时候无意义),虽然arg是long的,但用户可以通过强制转换使用为指针,这时候就需要cpoy_from_user,copy_to_user等取出ioctl需要的真正参数(这时候通常配合access_ok使用)。
原型:copy_to_user(void __user *to, const void *from, unsigned long n)
copy_from_user(void *to, const void __user *from, unsigned long n)
*/
{对应用户空间int ioctl(int fd,unsigned long cmd,...可有可无参数)}
//提示:『int access_ok(int type,const void *addr,unsigned long size)』第一个参数是VERIFY_READ或者VERIFY_WRITE用来表明是读用户内存还是写用户内存,addr是用户内存地址,size是操作的长度(如,sizeof(int))。
内存映射:
int (*mmap) (struct file *,struct vm_area_struct *)//将设备映射到进程虚拟地址空间中。
}
6.IO内存:{
申请:struct resource *request_mem_region(unsigned long start,unsigned long len,char *name)//这个函数申请一个从start开始,长度为len字节的内存区。如果成功返回非NULL,否则返回NULL。所有已经在使用的I/O内存在/proc/iomem中列出
映射:
void *ioremap(unsigned long phys_addr,unsigned long size)//返回值是指针,在访问I/O内存之前,必须进行物理地址的映射,ioremap函数具有这个功能。
访问I/O内存:
{
读:
unsigned ioread8(void *addr)
unsigned ioread16(void *addr)
unsigned ioread32(void *addr)
写:
void iowrite8(u8 value,void *addr)
void iowrite16(u16 value,void *addr)
void iowrite32(u32 value,void *addr)
}
解除映射和释放内存:
void iounmap(void *addr)//解除映射
void release_mem_region(unsigned long start,unsigned long len)//释放内存
}
}
大写宏大全:
{
MAJOR(dev_t dev)得到主设备号
MINOR(dev_t dev)得到次设备号
THIS_MODULE 是一个struct module变量,代表当前模块,与current有几分相似,比如使用THIS_MODULE->state可以获得当前模块的状态。
ioctl类:{
_IO(type,nr) //没有参数的命令
_IOR(type,nr,datatype) //从驱动中读取数据
_IOW(type,nr,datatype) //往驱动中写数据
_IOWR(type,nr,datatype) //双向传送,type和number成员作为参数被传递
type:幻数,nr:序号,datatype:类型(如int)
MEM_IOC_MAGIC幻数(不可重复):
{//一般使用
#define MEM_IOC_MAGIC 'm' //定义幻数
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int)
#define MEM_IOC_MAGIC _IOR(MEM_IOC_MAGIC,1,int)
}
-EINVAL(非法参数返回值)
}
文件描述符集#include <sys/select.h>
{
void FD_SET(int fd,fd_set *fdset) //将文件描述法fd添加到文件描述符集fdset中
void FD_CLR(int fd,fd_set *fdset) //将文件描述法fd从文件描述符集fdset中清除
void FD_ZERO(fd_set *fdset) //清空文件描述符fdset
void FD_ISSET(int fd,fd_set *fdset) //在调用select后使用FD_ISSET来检测文件描述符集fdset中的文件是否发生了变化
}
位掩码:POLLIN:设备可读;POLLRDNORM数据可读;POLLOUT设备可写,POLLWRNORM数据可写
TIF_NEED_RESCHED 内核可调度标志
}
结构体大全:
{
1.file 代表一个打开的文件。系统每打开一次文件都对应一个 struct file(当文件关闭释放)。包含loff_t f_pos/*文件读写位置,lseek等函数会访问*/和 struct file_operations *f_op/*文件操作函数集合*/
2.inode 用来记录文件的物理信息,一个文件可对应多个 struct file,但值对应一个 inode。包含dev_t i_rdev/*设备号*/
3.file_operations 一个函数指针的集合,这些结构体成员指向驱动的操作函数,对不支持的操作保留为NULL。
4.struct cdev 描述字符设备的结构体。
5.struct list_head内核双向链表。
6.struct miscdevice//描述一个混杂设备,详见上
7.struct bus_type //描述总线,详见上
8.struct kobject //它与sysfs文件系统紧密相连,在内核中注册的每个kobject对象对应sysfs文件系统中的一个目录。详见上。
9.struct kobj_type//kobject结构体有一个成员指针指向这个结构体,该结构记录了kobject对象的一些属性
10. struct attribute //属性,属性文件可以是交流窗口
11. struct sysfs_ops //当用户读写属性文件的时候调用
12. struct kset //kset是具有相同类型的kobject的集合
13.struct kset_uevent_ops//和kset、kobject相关的热插拔事件
14.struct sched_class //调度类,包含下一个要执行的进程的task_struct指针,关系到调度方法
}
类型和常用变量大全:
{
类型:
dev_t 设备号,unsigned int ,高12位为主设备号,低20位为次设备号。
变量:
jiffies当时钟发生中断就加1,记录了linux从启动后发生中断的次数。驱动程序常常利用jiffies来计算不同事件间的时间间隔。
{
//延迟执行:如果对延迟的精度要求不高,最简单的实现方法-忙等待
unsigned long j =jiffies + jit_delay *HZ;//HZ是自己定的,假如是1000,jit_delay是1,那么就对应1秒
while(jiffies < j);
//after j secs
}
进程相关{
volatile long state; //进程状态
pid_t pid; //进线程号,最大值10亿
struct mm_struct *mm 进程用户空间描述指针,内核线程该指针为空
unsigned int policy 该进程的调度策略
int prio 优先级,数字大不优先
int static_prio
current指向正在运行的进程的task_struct
preempt_count 内核抢占计数,为0时候可抢占。
}
}
提示:
笔记中proc略,异常分析略,系统调用略。
凡是进程调用或进程本身都属于进程上下文,例如应用程序调用系统的read函数,(这是系统调用),也属于进程上下文。中断上下文通常意味着外部设备或定时器的某种触发机制,属于中断上下文。