《Linux设备驱动开发详解》第2版 宋宝华 编著
Bought on Dec 1, 2010, Noted on 2015.6
【声明】本文大部分内容摘自《Linux设备驱动开发详解》第2版,或者网上搜索,故不单独注明内容出处
第一篇 Linux设备驱动入门
设备的分类
字符设备;块设备;网络设备
其中网络设备不会映射到文件系统中的文件和目录,而是面向数据包的接受和发送
处理器分类
CPU的体系结构:冯诺依曼机构和哈弗结构(程序和数据分开存储,包括独立的总线)
从指令集角度分:RISC(ARM,MIPS,PowerPC)和CISC(IA x86)
按应用领域区分:通用处理器(GPP:general-purpose preprocessor),DSP,ASP/ASIC(Application specific integriated circuit)
Linux 2.6内核特点
1. 新的调度器,在高负载情况下执行极其出色
2. 内核任务可抢占,提高系统的实时性,使得鼠标和键盘事件得到更快的响应
3. 改进的线程模型,线程操作速度得以提高,可以处理任意数量的线程
4. 虚拟内存,增加r-map(方向映射),显著改善虚拟内存在一定程度负载下的性能
5. 音频。弃用OSS,改用ALSA
Linux Kernel's components
SCHED(进程调度)MM(内存管理)VFS(虚拟文件系统)NET(网络接口)IPC(进程间通信)
其中网络接口分为网络协议和网络驱动程序
Coding style
TAB 8 characters
if/for one line, without { }
switch and case aligning
for (i = 0; i < 10; i++) {
.......................
}
GNU C & ANSI C
GNU 是ANSI C的扩展语法
1. 零长度和变量长度数组
char data[0]; ....... then data[i]=......
int main(int argc, char *argv[])
{
int i, n = argc;
double x[n];
}
2. case的范围
like
switch (ch) {
case '0' ... '9': xxx
case 'a' ... 'f': xxx
}
3. typeof 获取变量的type
e.g. const typeof(x) _x = (x);
4. 可变参数宏
e.g. #define pr_debug(fmt, arg...) \
printk(fmt, ##arg)
其中##是为了处理参数为零的情况,去除掉fmt后面的逗号
5. 标号元素
通过指定索引或者结构体成员名,允许初始化值以任意顺序出现
struct file_operations ext2_file_operations = {
llseek: genernic_file_llseek,
ioctl: ext2_ioctl,
.....
}
6. built-in function in GCC
For example, __builtin_return_address(LEVEL)
Checking out GNU GCC manual
第二篇 Linux设备驱动核心理论
Linux File System
System calls of FS
int create(const char *filename, mode_t mode);
int umask(int newmask);
int open(const char *filename, int flags, mode_t mode);
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);
int lseek(int fd, offset_t offset, int whence);
int close(int fd);
File operation API of C library
dependence on different OS
FILE *fopen(const char *path, const char *mode);
int fgetc(FILE *stream);
int fputc(int c, FILE *stream);
char *fgets(char *s, int n, FILE *stream);
int fprintf(FILE *stream, const char *format, ... );
int fscanf(FILE *steam, const char *format, ... );
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream);
int fseek(FILE *stream, long offset, int whence);
int fclose(FILE *stream);
struct file & inode
file结构体代表一个打开的文件或者设备。
file->f_mode标识文件的读写权限,file->f_flags标识可反映阻塞和非阻塞
inode结构体是linux管理文件系统的最基本单位,记录文件各类属性信息,其中i_rdev字段包含设备编号,包含major and minor number。一般major对应驱动,minor对应该驱动的设备序号
sysfs file system
这个VFS提供了包含所有系统硬件的层级视图,展示设备驱动模型各组件的层次关系,大致分三类:bus,devices,class。
三个重要结构体分别描述bus,driver,device: bus_type,device_driver,device.
device and driver's registration is separately. when any one registration, it will try to match another one via match() routine of bus_type();
Linux Character device
Main function of cdev
cdev struct describes character device information.
MAJOR(dev_t dev)
MINOR(dev_t dev)
MKDEV(int major, int minor)
void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
void cdev_put(strcut cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev*);
procedure of character device initialization
1. application device number
2. registration cdev
data transmit
copy_from_user()
copy_to_user()
put_user()
get_user()
ioctl command format
Device type + no + direction + size
(Device type is magic number, see more in ioctl-number.txt)
_IO()
_IOR()
_IOW()
_IOWR()
private_data
struct file {
....
void *private_data;
}
private_data存储驱动的私有数据,一般在驱动probe时动态开辟内存,以便多个设备共用一个驱动使用;不过随着device——tree的应用,越来越多的驱动数据放在device_tree中。
Concurrency & race condition
并发和竞态
critical sections临界资源包含:HW,static/global variables
竞态发生的情况:SMP,多线程,包括可抢占式内核,中断
避免竞态的手段:中断屏蔽,原子操作,自旋锁,信号量
中断屏蔽
可以避免新的中断到来,也可以避免内核抢占的发生(进程调度依赖于中断来实现)
local_irq_disable()
local_irq_enable()
local_irq_save(flags)
local_irq_restore(flags)
local_bh_disable() //disable 中断底半部
local_bh_enable()
原子操作
For integer operand
atomic_set
atomic_read
atomic_add
atomic_sub
atomic_inc_and_test
atomic_dec_and_test
atomic_sub_and_test
atomic_inc_return
atomic_sub_return
atomic_dec_return
atomic_add_return
For bit operand
set_bit
clear_bit
change_bit
test_bit
test_and_set_bit
test_and_clear_bit
test_and_change_bit
自旋锁
Spin lock
spinlock_t lock;
spin_lock_init(lock);
spin_lock(lock)
spin_trylock(lock) return immediately even lock failure
spin_unlock(lock)
NOTE: 在自旋锁持有期间,内核抢占被禁止,主要针对SMP和单CPU可抢占内核,但是依然受到中断和底半部的影响。所以自旋锁往往结合中断使能函数一同使用,如下:
spin_lock_irq
spin_unlock_irq
spin_lock_irqsave
spin_unlock_irqrestore
spin_lock_bh
spin_unlock_bh
NOTE: spin lock实际上是忙等,CPU不做任何事情,非常消耗CPU,所以只能用于很短时间的等待,往往用于等待硬件的场景
spin lock可能导致系统死锁,比如递归使用同一个自旋锁,即拿到锁后,没有释放而再次拿锁
spin lock锁定期间,不能调用可能引起进程调度的函数。如果进程获得自旋锁后再阻塞,如调用了copy_from_user(), copy_to_user(),则可能导致内核的崩溃
读写自旋锁
rwlock
允许读的并发,禁止写的同时进行
rwlock_init
read_lock
read_lock_ireqsave
read_lock_irq
read_unlock
read_unlock_irqrestore
read_unlock_irq
write_lock
write_lock_irqsave
write_lock_irq
write_trylock
write_unlock
write_unlock_irqrestore
write_unlock_irq
For example,
rwlock_t lock;
rwlock_init(&lock);
read_lock(&lock);
.....
read_unlock(&lock);
write_lock_irqsave(&lock, flags);
....
write_unlock_irqrestore(&lock, flags);
循序锁
seqlock
对读写锁的一种优化,读执行单元不会被写执行单元阻塞,写执行单元也不需要等待读执行单元完成读操作后才进行写操作,即读写可以同时操作,只不过如果读的过程中发生了写操作,需要重新读取数据;写执行单元之间是互斥的。
因为顺序锁允许读写同时进行,大大提高了并发性,对于读写同时进行的概率比较小的情况下,性能非常好。
NOTE:顺序锁有个限制,要求被保护的共享资源不含有指针,因为写执行单元可能使得指针失效,但读执行单元如果正要访问该指针,将导致oops
For example,
//write operation
write_seqlock(&seqlock_a);
...
write_sequnlock(&seqlock_a);
//read operation
read_seqbegin(...); //返回顺序锁的当前顺序号
read_seqretry(...); //检查资源是否被复写,如是,则重读
e.g.
do {
seqnum = read_seqbegin(&seqlock_a);
/* execute read operations */
....
} while(read_seqretry(&seqlock_a, seqnum));
读-拷贝-更新
RCU(Read-Copy Update)
原理:写操作需要先拷贝一个副本,先对副本进行修改,然后再适当的时机拷贝(update)回原有数据。这个时机就是所有引用该数据的CPU都退出对共享数据的操作的时候。读执行单元没有任何同步开销,而写操作单元的同步开销则取决于使用的写执行单元间同步机制。
RCU可以看做读写锁的高性能版本,既允许多个读单元并发,又允许多个读执行单元和多个写执行单元同时并发。但是对于写比较多的并发情况,写执行单元之间的同步开销也随之加大,必定需要用某种锁机制来同步并行的多个写操作<