帖子: LDD3学习
LDD3
步骤
根据ldd3,总结写一个简单驱动的步骤
``` 1. 基本头文件<moduel,moduleparam.init>,基础函数头文件<kernel,slab,fs,error,types,proc+fs,etc>,属于哪一类设备及kernel对应头文件cdev,模块初始化函数module_init module_exit 2. 输出参数module_param,版本号MODULE_VERSION作者MODULE_AUTHOR等 3. 补全init函数:分主次设备号、注册设备device(malloc,cdev_add),最后call init其他设备的函数(没懂?)
```
建立和运行模块
- 安装、卸载模块,insmod、rmmod,modprobe安装一组依赖模块。注意错误处理、并发可重入
- 导出符号表:EXPORT_SYMBOL
- 用户空间模块:(优)额外库、方便调试、安全 (劣)慢、特定设备、限制操作
- 初始化参数、文件接口。
module_param() 设置可见性和权限,modprobe配置文件(/etc/modprobe.conf) - kernel Makefile obj-m obj-y
#include <linux/init.h> //initialize
__init module_init()
__exit module_clean()
#include <linux/sched.h>
struct task_struct *current //sleeping and def
current->pid
current->comm
#include <version.h> // verison match
#include <linux/moduleparam.h>
#include <linux/kernel.h> //print pipe
字符驱动
devices
-
设备号:主号动态分配(alloc_chrdev_region),静态分配(devices.txt表 /proc/devices),次号默认分配。在dev和proc中
1. 如果已知主设备号dev,使用register_chrdev_region(几乎不用) 2. 如果不知道主设备号,使用alloc_chrdev_region 3. 主编号通常是驱动,次编号通常是驱动指的设备(当然可以有多个),所以次编号可以作为设备数组索引(常见的实现方式)
设备的数据结构
- 文件操作、文件结构:一个可打开的文件,open才创建,close引用计数为0执行release。inode就是可见的文件夹或文件 如果是device的ll就能show info
- copy_to_user
调试
- syslog 设置日志等级(外部可见) klog内核(缓冲区在/proc/kmsg klogctl设置) 自定义消息要转存
- 驱动消息打开关闭(给了一个预编译宏)
- 限速:/proc/sys/kern/printk_ratelimit
- 重定义输出tty print_dev_t打印编号
- proc少用 sysfs建议 ioctl好、快、可隐藏
- proc (内核文件都是先定义打开的动作 再实体到文件系统create_proc_read_entry)proc_create 和 proc_create_data
- 系统挂起
驱动(schedule()) 用户(魔术 sysrq 键:通过键盘 Alt+SysRq+
- 调试:gdb kdb kgdb 用户模式 Linux 移植、 Linux Trace Toolkit
- 并发可重入:spinlock、(mutex、rwlock)、semaphore、completion、atomic_t、seqlock、rcu
并发
内核信号量semaphore
常规初始化
void sema_init(struct semaphore *sem, int val);
定义为互斥锁
DECLARE_MUTEX(name);
DECLARE_MUTEX_LOCKED(name);
定义动态互斥锁(使用下列函数时初始化)
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
P操作
1.void down(struct semaphore *sem);
2.P操作失败了触发中断:int down_interruptible(struct semaphore *sem);
3.立即返回:int down_trylock(struct semaphore *sem);
r-w semaphore
常规初始化
void init_rwsem(struct rw_semaphore *sem);
读写锁(读锁,共享读不可写,写锁得不到;写锁,排他)
void down_read/write(struct rw_semaphore *sem);
int down_read/write_trylock(struct rw_semaphore *sem);
void up_read/write(struct rw_semaphore *sem);
写完立即通知所有读者
void downgrade_write(struct rw_semaphore *sem);
Completions 完成通知
静态初始化:
DECLARE_COMPLETION(my_completion);
动态初始化:
struct completion my_completion;
/* ... */
init_completion(&my_completion);
等待完成:
void wait_for_completion(struct completion *c);
发出完成:
void complete(struct completion *c); **可重用**
void complete_all(struct completion *c); **不可重用**
如果线程需要等待complete并退出
void complete_and_exit(struct completion *c, long retval);
自旋锁
原子操作
seqlock
RCU read-copy-update
RCU保护的数据类型只能是指针和list,在这里更新RCU的api操作:
a. rcu_read_lock() //标记读者临界区的开始
b. rcu_read_unlock() //标记读者临界区的结束
c. synchronize_rcu() / call_rcu() //等待Grace period结束后进行资源回收
d. rcu_assign_pointer() //Updater使用这个宏对受RCU保护的指针进行赋值
e. rcu_dereference() //Reader使用这个宏来获取受RCU保护的指针
bitops
tips:一个线程不能lock两次同一个、多个锁加锁顺序相同