34. LDD3 linux设备驱动

34.1. 主设备号,次设备号

主设备号一般用来标识设备的驱动。 次设备号内核用来区分实际设备。

printk 不支持浮点数

The printk function used in hello.c earlier, for example, is the version of printf
defined within the kernel and exported to modules. It behaves similarly to the original
function, with a few minor differences, the main one being lack of floating-point suppor

内核中和驱动相关重要得数据结构

  1. file_operations
struct file_operations

file_operations是文件操作的抽象,注册了用户态对文件的读写,在内核是由哪些函数具体实现的。

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iopoll)(struct kiocb *kiocb, bool spin);

这里仅节选了一部分,完整请查看文件 本站fs.h github fs.h

用户态的read调用 [1]

#include <unistd.h>

/** 尝试从文件fd读取count字节到buf
*/
ssize_t read(int fd, void *buf, size_t count);

内核态的read 实现

/** @brief 文件的读函数
 *  @file 第一个参数,内核态代表打开的文件,不会在用户态出现,也是重要的数据结构的第三个。
 *  @_user 第二个参数,用户态中程序的地址或者是libc中的地址
 *  @size_t 第三个参数,用户态中的地址的大小
 *  @loff_t 第四个参数,内核态打开文件的文件的偏移量
 */
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  1. inode
struct inode

代表内核内的任意一个文件

struct inode {
	umode_t			i_mode;
	unsigned short		i_opflags;
	kuid_t			i_uid;
	kgid_t			i_gid;
	unsigned int		i_flags;
	dev_t			i_rdev;
	loff_t			i_size;
	struct timespec64	i_atime;
	struct timespec64	i_mtime;
	struct timespec64	i_ctime;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	unsigned short          i_bytes;
	u8			i_blkbits;
	u8			i_write_hint;
	blkcnt_t		i_blocks;
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	*i_bdev;
		struct cdev		*i_cdev;
		char			*i_link;
		unsigned		i_dir_seq;
	};

  1. file
struct file

代表内核中每一个被打开的文件, 而inode节点代表每一个存在的文件, 很多打开的文件可以指向同一个inode

struct file {
	union {
		struct llist_node	fu_llist;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;

	/*
	 * Protects f_ep_links, f_flags.
	 * Must not be taken from IRQ context.
	 */
	spinlock_t		f_lock;
	enum rw_hint		f_write_hint;
	atomic_long_t		f_count;
	unsigned int 		f_flags;
	fmode_t			f_mode;
	struct mutex		f_pos_lock;
	loff_t			f_pos;
	struct fown_struct	f_owner;
	const struct cred	*f_cred;
	struct file_ra_state	f_ra;

34.2. 创建字符设备文件有两种方式

  1. 使用 mknod,然后可以直接用rm删除
mknod  filename type  major  minor

    filename :  设备文件名
    type        :  设备文件类型
    major      :   主设备号
    minor      :   次设备号
  1. 在代码中手动创建
struct class *cdev_class;
cdev_class = class_create(owner,name)
device_create(_cls,_parent,_devt,_device,_fmt)

device_destroy(_cls,_device)
class_destroy(struct class * cls)

引用空指针, 通常会导致oops。

34.3. 使用/proc文件系统

ldd 使用的函数是比较旧的,所以找了 linux kernel workbook的例子 [2]

  1. 首先在驱动中实现接口

lld3中提到使用read_proc, 实际上在proc_fs.h中已经找不到read_proc,在proc_fs.h中 定义了proc_read [3]

//已经弃用
int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);

//现使用
ssize_t     (*proc_read)(struct file *, char __user *, size_t, loff_t *);
  1. 仍然需要创建file_operations
struct file_operations proc_fops = {
    .read = my_proc_read,
    .write = my_proc_write,
};
  1. 创建/proc下的文件。
//已经弃用
struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode,
                                            struct proc_dir_entry *base,
                                            read_proc_t *read_proc,
                                            void *data);

//现在使用接口
extern struct proc_dir_entry *proc_create_data(const char *, umode_t,
                    struct proc_dir_entry *,
                    const struct proc_ops *,
                    void *);
//删除文件
remove_proc_entry("scullmem", NULL /* parent dir */);
  1. 一个完整的例子 [4]
static int __init initialization_function(void){
    struct proc_dir_entry *ret = NULL;
    printk("%s: installing module\n", modname);
    ret = proc_create_data(modname, 0666, NULL, &proc_fops, NULL);
    if(!ret) printk("useproc error\n");
    return 0;
}
static void __exit deinitialization_function(void){
    remove_proc_entry(modname, NULL);
    printk("%s, removing..\n",modname);
}

插入模块窗口

user1@ubuntu:~/fish_kernel_module/proc_module$ sudo insmod useproc.ko
user1@ubuntu:~/fish_kernel_module/proc_module$
user1@ubuntu:~/fish_kernel_module/proc_module$ sudo lsmod | grep useproc
useproc               114688  0
user1@ubuntu:~/fish_kernel_module/proc_module$ sudo dmesg | tail
[18467.056449] useproc: installing module
[18492.543355] msg has been save: kkkkkkkkkkkkkk
[18494.560928] read argument: 00000000d6613f3c, 00000000df21919d, 256, 0
[18494.560930] read data:kkkkkkkkkkkkkk

用户态程序测试窗口:

user1@ubuntu:~/fish_kernel_module/proc_module$ sudo ./test_proc_module.out
[sudo] password for user1:
Starting device test code example...
Type in a short string to send to the kernel module:
kkkkkkkkkkkkkkk
Writing message to the device [kkkkkkkkkkkkkkk].
Press ENTER to read back from the device...

Reading from the device...
The received message is: [kkkkkkkkkkkkkk]
End of the program
[1]http://man7.org/linux/man-pages/man2/read.2.html
[2]https://lkw.readthedocs.io/en/latest/doc/05_proc_interface.html
[3]https://github.com/torvalds/linux/blob/master/include/linux/proc_fs.h
[4]https://github.com/LyleLee/fish_kernel_module/tree/master/proc_module

34.4. 信号量实现

信号量实现临界区互斥

include/linux/semaphore.h
/* Please don't access any members of this structure directly */
struct semaphore {
    raw_spinlock_t          lock;
    unsigned int            count;
    struct list_head        wait_list;
};
include/linux/rwsem.h
struct rw_semaphore {
    atomic_long_t count;
    atomic_long_t owner;
    struct optimistic_spin_queue osq; /* spinner MCS lock */
    raw_spinlock_t wait_lock;
    struct list_head wait_list;
}

extern void down_read(struct rw_semaphore *sem); //申请锁
extern int down_read_trylock(struct rw_semaphore *sem);
extern void up_read(struct rw_semaphore *sem);

34.5. complettion

complettion提供等待条件成立的机制,例如一个进程wait_for_complete