1 分钟阅读

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+(部分笔记本上是Alt+Fn+PrtSrc+) 、常见入口:/proc/sysrq-trigger)

  • 调试: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两次同一个、多个锁加锁顺序相同