公司做网站的优点,申请公司注册流程,制作网页的第一步是什么,企业名录免费大全1. 内核工具和辅助函数
1.1宏container_of
container_of函数可以通过结构体的成员变量检索出整个结构体
函数原型#xff1a;
/*
pointer 指向结构体字段的指针
container_type 结构体类型
container_field 结构体字段名称
返回值是一个指针
*/
container_of(pointer, con…1. 内核工具和辅助函数
1.1宏container_of
container_of函数可以通过结构体的成员变量检索出整个结构体
函数原型
/*
pointer 指向结构体字段的指针
container_type 结构体类型
container_field 结构体字段名称
返回值是一个指针
*/
container_of(pointer, container_type,container_field); struct mcp23016 {struct i2c_client *client;struct gpio_chip chip;
};
static inline struct mcp23016* to_mcp23016(struct gpio_chip *gc)
{return container_of(gc,struct mcp23016,chip);
}1.2 链表
内核开发者只实现了循环双链表因为这个结构能够实现FIFO和LIFO并且内核开发者要保持最少代码。 为了支持链表代码中要添加的头文件是linux/list.h。内核中链表实现核心部分的数据结构 是struct list_head其定义如下
struct list_head {struct list_head *next, *prev;
};实例代码
#include linux/list.h
struct car {int door_number;char *color;char *model;struct list_head list; /*内核的表结构 */
};struct car *redcar kmalloc(sizeof(*car),GFP_KERNEL);
struct car *bluecar kmalloc(sizeof(*car),GFP_KERNEL);
/* 初始化每个节点的列表条目*/
INIT_LIST_HEAD(bluecar-list);
INIT_LIST_HEAD(redcar-list);
/* 为颜色和模型字段分配内存并填充每个字段 */
list_add(redcar-list, carlist) ;
list_add(bluecar-list, carlist) ;链表初始化
struct list_head mylist;
INIT_LIST_HEAD(mylist);static inline void INIT_LIST_HEAD(struct list_head *list)
{list-next list;list-prev list;
}添加链表节点
static inline void list_add(struct list_head *new, struct list_head *head)
{__list_add(new, head, head-next);
}/*新节点都是添加在head的后面而不是最后*/
static inline void __list_add(struct list_head *new,struct list_head *prev, struct list_head *next)
{next-prev new;new-next next;new-prev prev;prev-next new;
}删除链表节点
void list_del(struct list_head *entry);
list_del(redcar-list);链表遍历
list_for_each_entry(pos, head, member)1.3等待队列
等待队列链表锁
想要其入睡的每个进程都在该链表中排队因此被称作等待队列并进入睡眠状态直到条件变为真。等待队列可以被看作简单的进程链表和锁。
wait_event_interruptible不会持续轮询而只是在被调用时评估条件。如果条件为假则进程将进入TASK_INTERRUPTIBLE状态并从运行队列中删除。之后当每次在等待队列中调用wake_up_interruptible时都会重新检查条件。如果wake_up_interruptible运行时发现条件为真则等待队列中的进程将被唤醒并将其状态设置为TASK_RUNNING。进程按照它们进入睡眠的顺序唤醒。要唤醒在队列中等待的所有进程应该使用wake_up_interruptible_all。
如果调用了wake_up或wake_up_interruptible并且条件仍然是FALSE则什么都不会发生。如果没有调用wake_up或wake_up_interuptible进程将永远不会被唤醒。下面是一个等待队列的例子
#include linux/module.h
#include linux/init.h
#include linux/sched.h
#include linux/time.h
#include linux/delay.h
#includelinux/workqueue.hstatic DECLARE_WAIT_QUEUE_HEAD(my_wq);
static int condition 0;
/* 声明一个工作队列*/
static struct work_struct wrk;
static void work_handler(struct work_struct *work)
{printk(Waitqueue module handler %s\n,__FUNCTION__);msleep(5000);printk(Wake up the sleeping module\n);condition 1;wake_up_interruptible(my_wq);
}
static int __init my_init(void)
{printk(Wait queue example\n);INIT_WORK(wrk, work_handler);schedule_work(wrk);//将work_handler加入工作队列等待调用(系统自动调)printk(Going to sleep %s\n, __FUNCTION__);wait_event_interruptible(my_wq, condition !0);//卡在这里直到work_handler被调用才继续执行pr_info(woken up by the work job\n);return 0;
}
void my_exit(void)
{printk(waitqueue example cleanup\n);
}
module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR(John Madieujohn.madieufoobar.com);
MODULE_LICENSE(GPL);等待队列定义
动态定义
wait_queue_head_t my_wait_queue;
init_waitqueue_head(my_wait_queue);
静态定义
DECLARE_WAIT_QUEUE_HEAD(name)
阻塞
/*
* 如果条件为false则阻塞等待队列中的当前任务进程
*/
int wait_event_interruptible(wait_queue_head_t q, CONDITION);
解除阻塞
/*
* 如果上述条件为true则唤醒在等待队列中休眠的进程
*/
void wake_up_interruptible(wait_queue_head_t *q);工作队列定义
INIT_WORK(wrk, work_handler);
工作队列调度
schedule_work(wrk);//等待系统调用工作队列和等待队列的区别工作队列会绑定函数执行等待队列不会绑定函数单纯的当进程同步使用。1.4内核延迟与定时器
Jiffy是在linux/jiffies.h中声明的内核时间单位。为了理解Jiffy需要引入一个新的常量HZ它是jiffies在1s内递增的次数。每个增量被称为一个Tick。换句话说HZ代表Jiffy的大小。HZ取决于硬件和内核版本也决定了时钟中断触发的频率。
1.4.1标准定时器
定时器API
定时器在内核中表示为timer_list的一个实例
#include linux/timer.h
struct timer_list {struct list_head entry; //双向链表unsigned long expires; //以jiffies为单位绝对值struct tvec_t_base_s *base;void (*function)(unsigned long);unsigned long data;
);
1.设置定时器
void setup_timer( struct timer_list *timer, void (*function)(unsigned long),unsigned long data);
也可以使用这个函数
void init_timer(struct timer_list *timer);2.设置过期时间。当定时器初始化时需要在启动回调之前设置它的过期时间
int mod_timer( struct timer_list *timer,unsigned long expires);3.释放定时器。定时器用过之后需要释放
void del_timer(struct timer_list *timer);
int del_timer_sync(struct timer_list *timer);样例
#include linux/init.h
#include linux/kernel.h
#include linux/module.h
#include linux/timer.h
static struct timer_list my_timer;
void my_timer_callback(unsigned long data)
{printk(%s called (%ld).\n, __FUNCTION__,jiffies);
}
static int __init my_init(void)
{int retval;printk(Timer module loaded\n);setup_timer(my_timer, my_timer_callback,0);printk(Setup timer to fire in 300ms(%ld)\n, jiffies);retval mod_timer( my_timer, jiffies msecs_to_jiffies(300) );if (retval)printk(Timer firing failed\n);return 0;
}
static void my_exit(void)
{int retval;retval del_timer(my_timer);/* 定时器仍然是活动的1或没有0*/if (retval)printk(The timer is still inuse...\n);pr_info(Timer module unloaded\n);
}
module_init(my_init);
module_exit(my_exit);1.4.2高精度定时器
高精度定时器由内核配置中的CONFIG_HIGH_RES_TIMERS选项启用其精度达到微秒取决于平台最高可达纳秒而标准定时器的精度则为毫秒。标准定时器取决于HZ因为它们依赖于jiffies而HRT实现是基于ktime。
#include linux/hrtimer.h
struct hrtimer {struct timerqueue_node node;ktime_t _softexpires;enum hrtimer_restart (*function)(structhrtimer *);struct hrtimer_clock_base *base;u8 state;u8 is_rel;
};1.初始化hrtimer。hrtimer初始化之前需要设置ktime它代表持续时间。
void hrtimer_init(struct hrtimer *time,clockid_t which_clock,enum hrtimer_mode mode);
2.启动hrtimer
int hrtimer_start( struct hrtimer *timer,ktime_t time,const enum hrtimer_mode mode);
/*mode代表到期模式。对于绝对时间值它应该是HRTIMER_MODE_ABS对于相对于现在的时间值应该是HRTIMER_MODE_REL。*/
3.取消hrtimer。可以取消定时器或者查看是否可能取消它
int hrtimer_cancel( struct hrtimer *timer);
int hrtimer_try_to_cancel(struct hrtimer *timer);
/*这两个函数当定时器没被激活时都返回0激活时返回1。这两个函数之间的区别是如果定时器处于激活状态或其回调函数正在运行则hrtimer_try_to_cancel会失败返回-1而hrtimer_cancel将等待回调完成。*/
用下面的函数可以独立检查hrtimer的回调函数是否仍在运行
int hrtimer_callback_running(struct hrtimer *timer);
1.5内核锁机制
1.5.1互斥锁
struct mutex {
/*
1: 解锁, 0: 锁定, negative: 锁定, 可能的等待
其结构中有一个链表类型字段wait_list睡眠的原理是一样的
*/atomic_t count;spinlock_t wait_lock;struct list_head wait_list;
[...]
};
1.声明
静态声明
DEFINE_MUTEX(my_mutex);
动态声明
struct mutex my_mutex;
mutex_init(my_mutex);2.上锁
void mutex_lock(struct mutex *lock);
int mutex_lock_interruptible(struct mutex *lock); //驱动程序可以被所有信号中断,推荐
int mutex_lock_killable(struct mutex *lock); //只有杀死进程的信号才能中断驱动程序3.解锁
void mutex_unlock(struct mutex *lock);有时可能需要检查互斥锁是否锁定。为此使用int mutex_is_locked(struct mutex *lock)函数
这个函数只是检查互斥锁的所有者是否为空NULL。还有一个函数int mutex_trylock(struct mutex *lock)如果还没有锁定则它获取互斥锁并返回1否则返回0。实例
struct mutex my_mutex;
mutex_init(my_mutex);
/* 在工作或线程内部*/
mutex_lock(my_mutex);
access_shared_memory();
mutex_unlock(my_mutex)互斥锁使用须知
一次只能有一个任务持有互斥锁这其实不是规则而是事实。多次解锁是不允许的。它们必须通过API初始化。持有互斥锁的任务不可能退出因为互斥锁将保持锁定可能的竞争者会永远等待将睡眠。不能释放锁定的内存区域。持有的互斥锁不得重新初始化。由于它们涉及重新调度因此互斥锁不能用在原子上下文中如Tasklet和定时器。
与wait_queue一样互斥锁也没有轮询机制。每次在互斥锁上调用mutex_unlock时内核都会检查wait_list中的等待者。如果有等待者则其中的一个且只有一个将被唤醒和调度它们唤醒的顺序与它们入睡的顺序相同。
1.5.2自旋锁
spinlock_t my_spinlock;
spin_lock_init(my_spinlock);
static irqreturn_t my_irq_handler(int irq, void *data)
{unsigned long status, flags;/*spin_lock_irqsave函数会在获取自旋锁之前禁止当前处理器调用该函数的处理器上中断。spin_lock_irqsave在内部调用local_irq_save (flags)和preempt_disable()前者是一个依赖于体系结构的函数用于保存IRQ状态后者禁止在相关CPU上发生抢占。*/spin_lock_irqsave(my_spinlock, flags);status access_shared_resources();spin_unlock_irqrestore(gpio-slock, flags); //释放锁return IRQ_HANDLED;
}自旋锁与互斥锁的区别 互斥锁保护进程的关键资源而自旋锁保护IRQ处理程序的关键部分。 互斥锁让竞争者在获得锁之前睡眠而自旋锁在获得锁之前一直自旋循环消耗CPU。 鉴于上一点自旋锁不能长时间持有因为等待者在等待取锁期间会浪费CPU时间而互斥锁则可以长 时间持有只要保护资源需要因为竞争者被放入等待队列中进入睡眠状态。
1.6工作延时机制
延迟是将所要做的工作安排在将来执行的一种方法这种方法推后发布操作。显然内核提供了一些功能来实现这种机制它允许延迟调用和执行任何类型函数。下面是内核中的3项功能。
SoftIRQ执行在原子上下文 Tasklet 执行在原子上下文工作队列 执行在进程上下文
1.6.1 Tasklet
Tasklet构建在Softirq之上的下半部稍后将会看到这意味着什么机制。它们在内核中表示为struct tasklet_struct的实例
struct tasklet_struct
{struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};1、声明
动态声明
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);
静态声明
DECLARE_TASKLET( tasklet_example,tasklet_function, tasklet_data );
DECLARE_TASKLET_DISABLED(name, func, data);这两个函数有一个区别前者创建的Tasklet已经启用并准备好在没有任何其他函数调用的情况下被调度这通过将count字段设置为0来实现而后者创建的Tasklet被禁用通过将count设置为1来实现必须在其上调用tasklet_enable ()之后才可以调度这一Tasklet。2、启动和禁用TaskLet
void tasklet_enable(struct tasklet_struct *);
void tasklet_disable(struct tasklet_struct *); //本次tasklet执行后返回
void tasklet_disable_nosync(struct tasklet_struct *); //直接终止执行立刻返回3、TaskLet调度
void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t);
内核把普通优先级和高优先级的Tasklet维护在两个不同的链表中。tasklet_schedule将Tasklet添加到普通优先级链表中用TASKLET_SOFTIRQ标志调度相关的Softirq。tasklet_hi_schedule将Tasklet添加到高优先级链表中并用HI_SOFTIRQ标志调度相关的Softirq。高优先级Tasklet旨在用于具有低延迟要求的软中断处理程序。4、终止TaskLet
void tasklet_kill(struct tasklet_struct *t);#include linux/kernel.h
#include linux/module.h
#include linux/interrupt.h
char tasklet_data[]We use a string; but it could be pointer to a structure;
/* Tasklet处理程序只打印数据 */
void tasklet_work(unsigned long data)
{printk(%s\n, (char *)data);
}
DECLARE_TASKLET(my_tasklet, tasklet_work,(unsigned long)tasklet_data);static int __init my_init(void)
{
/*
* 安排处理程序
* 从中断处理程序调度Tasklet arealso
*/tasklet_schedule(my_tasklet);return 0;
}
void my_exit(void)
{tasklet_kill(my_tasklet);
}
module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR(John Madieujohn.madieugmail.com);
MODULE_LICENSE(GPL);1.6.2工作队列
作为延迟机制工作队列采用的方法与我们之前介绍的方法相反它只能运行在抢占上下文中。如果需要在中断下半部睡眠工作队列则是唯一的选择 。
#include linux/module.h
#include linux/init.h
#include linux/sched.h /* 睡眠 */
#include linux/wait.h /* 等待列队 */
#include linux/time.h
#include linux/delay.h
#include linux/slab.h /* kmalloc() */
#include linux/workqueue.h
//static DECLARE_WAIT_QUEUE_HEAD(my_wq);
static int sleep 0;
struct work_data {struct work_struct my_work;wait_queue_head_t my_wq;int the_data;
};
static void work_handler(struct work_struct *work)
{struct work_data *my_data container_of(work, struct work_data, my_work);printk(Work queue module handler: %s, data is %d\n, __FUNCTION__,my_data-the_data);msleep(2000);wake_up_interruptible(my_data-my_wq);kfree(my_data);
}
static int __init my_init(void)
{struct work_data * my_data;my_data kmalloc(sizeof(struct work_data),GFP_KERNEL);my_data-the_data 34;INIT_WORK(my_data-my_work, work_handler);init_waitqueue_head(my_data-my_wq);schedule_work(my_data-my_work);printk(Im goint to sleep ...\n);wait_event_interruptible(my_data-my_wq,sleep ! 0);printk(I am Waked up...\n);return 0;
}
static void __exit my_exit(void)
{printk(Work queue module exit: %s %d\n,__FUNCTION__, __LINE__);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(John Madieujohn.madieugmail.com );
MODULE_DESCRIPTION(Shared workqueue);1.7内核中断
注册中断函数
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long flags, const char *name, void *dev);
flag表示掩码IRQF_TIMER通知内核这个处理程序是由系统定时器中断触发的。IRQF_SHARED用于两个或多个设备共享的中断线。共享这个中断线的所有设备都必须设置该标志。如果被忽略将只能为该中断线注册一个处理程序。IRQ_ONESHOT主要在线程中断中使用它要求内核在硬中断处理程序没有完成之前不要重新启用该中断。在线程处理程序运行之前中断会一直保持禁用状态。
name内核用来标识/proc/interrupts和/proc/irq中的驱动程序
dev:其主要用途是作为参数传递给中断处理程序这对每个中断处理程序都是唯一的因为它用来标识这个设备。对于非共享中断它可以是NULL但共享中断不能为NULL。使用它的常见方法是提供设备结构因为它既独特又可能对处理程序有用。也就是说指向有一个指向设备数据结构的指针就足够了。struct my_data {struct input_dev *idev;struct i2c_client *client;char name[64];char phys[32];
};
static irqreturn_t my_irq_handler(int irq, void*dev_id)
{struct my_data *md dev_id;unsigned char nextstate read_state(lp);/* Check whether my device raised the irq or no */[...]return IRQ_HANDLED;
}
/* 在probe函数的某些位置 */
int ret;
struct my_data *md kzalloc(sizeof(*md), GFP_KERNEL);
ret request_irq(client-irq, my_irq_handler,IRQF_TRIGGER_LOW |IRQF_ONESHOT,DRV_NAME, md);
/* 在释放函数中*/
free_irq(client-irq, md);中断处理函数
static irqreturn_t my_irq_handler(int irq, void *dev)
中断返回值
IRQ_NONE设备不是中断的发起者在共享中断线上尤其会出现这种情况。
IRQ_HANDLED设备引发中断中断上半部处理紧急事件中断下半部用工作队列等延迟机制处理不紧急事件并且上半部处理的时候需要将所有中断全部紧张避免中断嵌套等上半部处理完再开启中断。
2.字符设备驱动程序
2.1设备文件操作
内核把文件描述为inode结构不是文件结构的实例inode结构在include/linux/fs.h中定义
struct inode {
[...]struct pipe_inode_info *i_pipe; /* 如果这是Linux内核管道则设置并使用 */struct block_device *i_bdev; /* 如果这是块设备则设置并使用 */struct cdev *i_cdev; /* 如果这是字符设备则设置并使用 */
[...]
}struct inode是文件系统的数据结构它只与操作系统相关用于保存文件无论它的类型是字符、块、管道等或目录从内核的角度来看目录也是文件是其他文件的入口点信息。
struct file结构也在include/linux/fs.h中定义是更高级的文件描述它代表内核中打开的文件依赖于低层的struct inode数据结构
struct file {
[...]struct path f_path; /* 文件路径 */struct inode *f_inode; /* 与此文件相关的inode */const struct file_operations *f_op; /* 可以在此文件上执行的操作 */loff_t f_pos; /* 此文件中光标的位置 *//* 需要tty驱动程序等 */void *private_data; /* 驱动程序可以设置的私有数据以便在文件操作之间共享这可以指向任何结构*/
[...]
}struct inode和struct file的区别在于inode不跟踪文件的当前位置和当前模式它只是帮助操作系统找到底层文件结构的内容管道、目录、常规磁盘文件、块/字符设备文件等。而struct file则是一个基本结构它实际上持有一个指向struct inode的指针它代表打开的文件并且提供一组函数它们与底层文件结构上执行的方法相关这些方法包括open、write、seek、read、select等。所有这一切都强化了UNIX系统的哲学一切皆是文件。
2.2分配和注册设备
字符设备在内核中表示为struct cdev的实例。在编写字符设备驱动程序时目标是最终创建并注册与struct file_operations关联的结构实例为用户空间提供一组可以在该设备上执行的操作函数。为了实现这个目标必须执行以下几个步骤。
1使用alloc_chrdev_region()保留一个主设备号和一定范围的次设备号。 2使用class_create()创建自己的设备类该函数在/sys/class中定义。 3创建一个struct file_operation传递给cdev_init每一个设备都需要创建并调用call_init和cdev_add()注册这个设备。 4调用device_create()创建每个设备并给它们一个合适的名字。这样就可在/dev目录下创建出设备。
2.3 ioctrl
ioctrl原型
_IO(MAGIC, SEQ_NO)
_IOW(MAGIC, SEQ_NO, TYPE)
_IOR(MAGIC, SEQ_NO, TYPE)
_IORW(MAGIC, SEQ_NO, TYPE)eep_ioctl.h
#ifndef PACKT_IOCTL_H
#define PACKT_IOCTL_H/*
* 需要为驱动选择一个数字以及每个命令的序列号
*/
#define EEP_MAGIC E
#define ERASE_SEQ_NO 0x01
#define RENAME_SEQ_NO 0x02
#define ClEAR_BYTE_SEQ_NO 0x03
#define GET_SIZE 0x04
/*
* 分区名必须是最大32字节
*/
#define MAX_PART_NAME 32
/*
* 定义ioctl编号
*/
#define EEP_ERASE _IO(EEP_MAGIC, ERASE_SEQ_NO)
#define EEP_RENAME_PART _IOW(EEP_MAGIC,RENAME_SEQ_NO, unsigned long)
#define EEP_GET_SIZE _IOR(EEP_MAGIC, GET_SIZE,int *)
#endiflong ioctl(struct file *f, unsigned int cmd,unsigned long arg);ioctrl步骤使用switch-case来调用自定义函数
/*
* 用户空间代码还需要包括定义ioctls的头文件这里是eep_iocl.h
*/
#include eep_ioctl.h
static long eep_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{int part;char *buf NULL;int size 1300;switch(cmd){case EEP_ERASE:erase_eepreom();break;case EEP_RENAME_PART:buf kmalloc(MAX_PART_NAME,GFP_KERNEL);copy_from_user(buf, (char *)arg,MAX_PART_NAME);rename_part(buf);break;case EEP_GET_SIZE:copy_to_user((int*)arg, size,sizeof(int));break;default:return -ENOTTY;return 0;
}用户程序调ioctrl
#include stdio.h
#include stdlib.h
#include fcntl.h
#include unistd.h
#include eep_ioctl.h /* our ioctl header file*/
int main()
{int size 0;int fd;char *new_name lorem_ipsum; /* 不超过MAX_PART_NAME */fd open(/dev/eep-mem1, O_RDWR);if (fd -1){printf(Error while opening the eeprom\n);return -1;} ioctl(fd, EEP_ERASE); /* 调用ioctl来擦除分区*/ioctl(fd, EEP_GET_SIZE, size); /* 调用ioctl获取分区大小 */ioctl(fd, EEP_RENAME_PART, new_name); /*调用ioctl来重命名分区 */close(fd);return 0;
}用户空间ioctl传递fd和cmd给内核空间内核空间根据cmd用switch-case调用对应的函数处理。cmd在头文件通过_IO(MAGIC, SEQ_NO)等io宏定义即可。
3.平台设备驱动程序
3.1平台驱动程序
I2C设备或SPI设备是平台设备但分别依赖于I2C或SPI总线而不是平台总线。
对于平台驱动程序一切都需要手动完成。平台驱动程序必须实现probe函数在插入模块或设备声明时内核调用它。在开发平台驱动程序时必须填写主结构struct platform_driver并用专用函数把驱动程序注册到平台总线核如下所示
static struct platform_driver mypdrv {.probe my_pdrv_probe, /*设备匹配后声明驱动程序时所调用的函数。*/.remove my_pdrv_remove,.driver {.name my_platform_driver,.owner THIS_MODULE,},
}在内核中注册平台驱动程序很简单只需在init函数中调用platform_driver_register()或platform_driver_probe()模块加载时。这两个函数之间的区别如下。 ·platform_driver_register()注册驱动程序并将其放入由内核维护的驱动程序列表中以便每当发现新的匹配时就可以按需调用其probe()函数。为防止驱动程序在该列表中插入和注册请使用下一个函数。
·platform_driver_probe()调用该函数后内核立即运行匹配循环检查是否有平台设备名称匹配如果匹配则调用驱动程序的probe()这意味着设备存在否则驱动程序将被忽略。此方法可防止延迟探测因为它不会在系统上注册驱动程序。在这里probe函数被放置在__init部分当内核启动完成时这个部分被释放从而防止了延迟探测并减少驱动程序的内存占用。如果100%确定设备存在于系统中请使用此方法
ret platform_driver_probe(mypdrv,my_pdrv_probe);平台驱动程序简单样例
#include linux/module.h
#include linux/kernel.h
#include linux/init.h
#include linux/platform_device.h
static int my_pdrv_probe (struct platform_device *pdev){pr_info(Hello! device probed!\n);return 0;
}
static void my_pdrv_remove(struct platform_device *pdev){pr_info(good bye reader!\n);
}
static struct platform_driver mypdrv {.probe my_pdrv_probe,.remove my_pdrv_remove,.driver {.name KBUILD_MODNAME,.owner THIS_MODULE,},
};
static int __init my_drv_init(void)
{pr_info(Hello Guy\n);/* 向内核注册*/platform_driver_register(mypdrv);return 0;
}
static void __exit my_pdrv_remove (void)
{pr_info(Good bye Guy\n);/* 从内核注销 */platform_driver_unregister(my_driver);
}
module_init(my_drv_init);
module_exit(my_pdrv_remove);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(John Madieu);
MODULE_DESCRIPTION(My platform Hello World module);每个总线都有特定的宏来注册驱动程序以下列表是其中的一部分。
·module_platform_driver(struct platform_driver)用于平台驱动程序专用于传统物理总线以外的设备
·module_spi_driver (struct spi_driver)用于SPI驱动程序。
·module_i2c_driver (struct i2c_driver)用于I2C驱动程序。
·module_pci_driver(struct pci_driver)用于PCI驱动程序。
·module_usb_driver(struct usb_driver)用于USB驱动程序。
·module_mdio_driver(struct mdio_driver)用于MDIO。3.2平台设备
完成驱动程序后必须向内核提供需要该驱动程序的设备。平台设备在内核中表示为struct platform_device的实例如下所示
struct platform_device {const char *name;u32 id;struct device dev;u32 num_resources;struct resource *resource;
};3.1设备驱动总线匹配
在匹配发生之前Linux会调用platform_match(struct device * dev,structdevice_driver * drv)。平台设备通过字符串与驱动程序匹配。根据Linux设备模型**总线元素是最重要的部分。每个总线都维护一个注册的驱动程序和设备列表。总线驱动程序负责设备和驱动程序的匹配。**每当连接新设备或者向总线添加新的驱动程序时总线都会启动匹配循环。
内核通过以下方式触发I2C总线匹配循环调用由I2C总线驱动程序注册的I2C核心匹配函数以检查是否有已注册的驱动程序与该设备匹配。如果没有匹配则什么都不会发生如果发现匹配则内核将通知通过netlink套接字通信机制设备管理器udev/mdev由它加载如果尚未加载与设备匹配的驱动程序。一旦驱动程序加载完成其probe()函数就立即执行。
1内核设备与驱动程序匹配函数
内核中负责平台设备和驱动程序匹配功能的函数在/drivers/base/platform.c中定义如下
static int platform_match(struct device *dev, struct device_driver *drv)
{struct platform_device *pdev to_platform_device(dev);struct platform_driver *pdrv to_platform_driver(drv);/* 在设置driver_override时只绑定到匹配的驱动程序*/if (pdev-driver_override)return !strcmp(pdev-driver_override,drv-name);/* 尝试一个样式匹配*/if (of_driver_match_device(dev, drv))return 1;/* 尝试ACPI样式匹配 */if (acpi_driver_match_device(dev, drv))return 1;/* 尝试匹配ID表 */if (pdrv-id_table)return platform_match_id(pdrv-id_table, pdev) ! NULL;/* 回退到驱动程序名称匹配 */return (strcmp(pdev-name, drv-name) 0);
}static const struct platform_device_id *platform_match_id(const struct platform_device_id *id, struct platform_device *pdev)
{while (id-name[0]) {if (strcmp(pdev-name, id-name) 0) {pdev-id_entry id;return id;} id;} return NULL;
}struct device_driver是每个设备驱动程序的基础。无论是I2C、SPI、TTY还是其他设备驱动程序它们都嵌入
struct device_driver元素。struct device_driver {const char *name;[...]const struct of_device_id *of_match_table;const struct acpi_device_id *acpi_match_table;
};4.设备树
4.1设备树机制
将选项CONFIG_OF设置为Y即可在内核中启用DT。要在驱动程序中调用DT API必须添加以下头文件
#include linux/of.h
#include linux/of_device.h4.1.1命名约定
每个节点都必须有 [
]形式的名称其中是一个字符串其长度最多为31个字符[ ]是可选的具体取决于节点代表是否为可寻址的设备。 i2c021a0000 {compatible fsl,imx6q-i2c, fsl,imx21-i2c;reg 0x021a0000 0x4000;[...]
};4.1.2处理中断
中断接口实际上分为两部分消费者端和控制器端。DT中用4个属性描述中断连接。控制器是为消费者提供中断线的设备。在控制器端有以下属性。·interrupt-controller为了将设备标记为中断控制器而应该定义的空布尔属性。·#interrupt-cells这是中断控制器的属性。它指出为该中断控制器指定一个中断要使用多少个单元。消费者是生成中断的设备。消费者绑定需要以下属性。·interrupt-parent对于产生中断的设备节点这个属性包含指向设备所连接的中断控制器节点的指针phandle。如果省略则设备从其父节点继承该属性。
interrupts 0 66 IRQ_TYPE_LEVEL_HIGH;
·0共享外设中断SPI用于核间共享的中断信号可由GIC路由至任意核。
·1专用外设中断PPI专用于单核的中断信号。·第二个单元格保存中断号。该中断号取决于中断线是PPI还是SPI。
·第三个单元这里的IRQ_TYPE_LEVEL_HIGH代表感知级别。所有可用的感知级别在include/linux/irq.h中定义。5.I2C客户端驱动程序 I2C驱动程序在内核中表示为struct i2c_driver的实例。I2C客户端代表设备本身由struct i2c_client结构表示。
5.1i2c_driver结构
struct i2c_driver {/* 标准驱动模型接口 */int (*probe)(struct i2c_client *, const struct i2c_device_id *);int (*remove)(struct i2c_client *);/* 与枚举无关的驱动类型接口 */void (*shutdown)(struct i2c_client *);struct device_driver driverconst struct i2c_device_id *id_table;
};struct i2c_driver结构包含并描述通用访问例程这些例程是处理声明驱动程序的设备所必需的而struct i2c_client则包含设备特有的信息如其地址。struct i2c_client结构表示和描述I2C设备
struct i2c_client {unsigned short flags; /* div., 见下文 */unsigned short addr; /* chip address - NOTE:7bit *//* 地址被存储在 _LOWER_ 7 bits */char name[I2C_NAME_SIZE];struct i2c_adapter *adapter; /* 适配器 */struct device dev; /* 设备结构 */int irq; /* 由设备发出的IR */struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)i2c_slave_cb_t slave_cb; /* 回调从设备 */
#endif
};5.2普通I2C通信
int i2c_master_send(struct i2c_client *client,const char *buf, int count);
int i2c_master_recv(struct i2c_client *client,char *buf, int count);几乎所有I2C通信函数都以struct i2c_client作为第一个参数。第二个参数包含要读取或写入的字节第三个参数表示要读取或写入的字节数。像任何读/写函数一样返回值是读/写的字节数。也可以使用以下方式处理消息传输
int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msg, int num);i2c_transfer发送一组消息其中每个消息可以是读取操作或写入操作也可以是它们的任意混合。请记住每两个事务之间没有停止位。 i2c_msg结构描述和表示I2C消息。它必须包含每条消息的客户端地址、消息的字节数和消息有效载荷。
struct i2c_msg {__u16 addr; /* 从设备地址 */__u16 flags; /* 信息标志 */__u16 len; /* msg长度 */__u8 *buf; /* 指向msg数据的指针 */
};样例
ssize_t eep_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
[...]int _reg_addr dev-current_pointer;u8 reg_addr[2];reg_addr[0] (u8)(_reg_addr 8);reg_addr[1] (u8)(_reg_addr 0xFF);struct i2c_msg msg[2];msg[0].addr dev-client-addr;msg[0].flags 0; /* 写入*/msg[0].len 2; /* 地址是2字节编码 */msg[0].buf reg_addr;msg[1].addr dev-client-addr;msg[1].flags I2C_M_RD; /* 读取*/msg[1].len count;msg[1].buf dev-data;
if (i2c_transfer(dev-client-adapter, msg,2) 0)pr_err(ee24lc512: i2c_transferfailed\n);
if (copy_to_user(buf, dev-data, count) !0) {retval -EIO;goto end_read;
}
[...]
}6.Regmap API—寄存器映射抽象
内核版本3.1中引入了Regmap API用于分解和统一内核开发人员访问SPI/I2C设备的方式。接下来的问题是无论它是SPI设备还是I2C设备只需要初始化、配置Regmap并流畅地处理所有读/写/修改操作 6.1.1使用Regmap API编程
Regmap API非常简单只需了解几个结构即可。这个API中的两个重要结构是struct regmap_config代表Regmap配置和struct regmapRegmap实例本身。
struct regmap_config在驱动程序的生命周期中存储Regmap配置这里的设置会影响读/写操作它是Regmap API中最重要的结构。
struct regmap_config {const char *name;int reg_bits;//寄存器地址中的位数int reg_stride;int pad_bits;int val_bits;bool (*writeable_reg)(struct device *dev,unsigned int reg);/*回调函数。如果提供则在需要写入寄存器时供Regmap子系统使用。*/bool (*readable_reg)(struct device *dev,unsigned int reg);bool (*volatile_reg)(struct device *dev,unsigned int reg);/*每当需要通过Regmap缓存读取或写入寄存器时调用它。*/bool (*precious_reg)(struct device *dev,unsigned int reg);regmap_lock lock;regmap_unlock unlock;void *lock_arg;int (*reg_read)(void *context, unsigned intreg,unsigned int *val);int (*reg_write)(void *context, unsigned intreg,unsigned int val);bool fast_io;unsigned int max_register;const struct regmap_access_table *wr_table;const struct regmap_access_table *rd_table;const struct regmap_access_table *volatile_table;const struct regmap_access_table *precious_table;const struct reg_default *reg_defaults;unsigned int num_reg_defaults;enum regcache_type cache_type;const void *reg_defaults_raw;unsigned int num_reg_defaults_raw;u8 read_flag_mask;u8 write_flag_mask;bool use_single_rw;bool can_multi_write;enum regmap_endian reg_format_endian;enum regmap_endian val_format_endian;const struct regmap_range_cfg *ranges;unsigned int num_ranges;
}7.内核内存管理 7.1Sla分配器
Slab分配器是kmalloc()所依赖的分配器。其主要目的是消除小内存分配情况下由伙伴系统引起的内存分配/释放造成的碎片加快常用对象的内存分配。
7.1.1伙伴系统
分配内存时所请求的是大小被四舍五入为2的幂伙伴分配器搜索相应的列表。如果请求列表中无项存在则把下一个上部列表其块大小为前一列表的两倍的项拆分成两部分称为伙伴。分配器使用前半部分而另一部分则向下添加到下一个列表中。这是一种递归方法当伙伴分配器成功找到可以拆分的块或达到最大块大小并且没有可用的空闲块时该递归方法停止。
7.1.2slab分配器概述
在介绍Slab分配器之前先定义它使用的一些术语。 ·Slab这是由数个页面帧组成的一块连续的物理内存。每个Slab分成大小相同的块用于存储特定类型的内核对象例如inode、互斥锁等。每个Slab是对象数组。