头文件
include
定义并初始化
wait_queue_head_t r_wait;
init_waitqueue_head(&cm_dev->r_wait);
wait_queue_head_t 表示等待队列头,等待队列wait时,会导致进程或线程被休眠,一个等待队列头中可以有很多的等待队列元素。每个元素绑定一个进程或者线程。这里绑定进程或者线程的目的,是为了在执行wakeup时,知道应该唤醒谁。
在Linux
字符设备驱动中,用户程序使用read()
、write()
相关函数时,内核会调用驱动程序中的的file_operations
结构体中对应的read()
、write()
函数。
file_operations
,其是一个函数指针的集合,用于存放我们定义的用于操作设备的函数的指针,如果我们不定义,它默认保留为NULL
。其中有最重要的几个函数,分别是open()
、read()
、write()
、ioctl()
,下面分别对其进行解析。
(1)打开设备
int (*open) (struct inode *, struct file *);
在操作设备前必须先调用open
函数打开文件,可以干一些需要的初始化操作。当然,如果不实现这个函数的话,驱动会默认设备的打开永远成功。打开成功时open
返回0。
(2)关闭设备
int (*release) (struct inode *, struct file *);
当设备文件被关闭时内核会调用这个操作,当然这也可以不实现,函数默认为NULL
。关闭设备永远成功。
函数原型:
ssize_t (*read) (struct file *filp, char __user *buffer, size_t size, loff_t *p);
参数含义:
file
结构体指针;返回值:
成功实际读取的字节数,失败返回负值。
如果该操作为空,将使得read系统调用返回负EINVAL失败,正常返回实际读取的字节数。
两个函数的作用分别是 从设备中获取数据及发送数据给设备,应用程序中与之对应的也有 write()
函数及 read()
函数:
len = read(fd,buf,len )
static ssize_t hello_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)
len = write(fd,buf,size)
static ssize_t hello_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos)
我们知道,应用程序工作在用户空间,而驱动工作在内核空间,二者不能直接通信的,那我们用何种方法进行通信呢?下面介绍一下内核中的 memcpy—copy_from_user和copy_to_user,虽然说内核中不能使用C
库提供的函数,但是内核也有一个memcpy
函数,用法跟C
库中的一样。
从字面意义上理解:user是指用户,即用户空间。
在file_operations
结构体中实现的write
函数,即2.2中的write()
函数中,用户空间向内核空间拷贝(写)数据需要使用copy_from_user
函数。而用户空间从内核空间读取数据需要使用copy_to_user
函数。两个函数定义在arch/arm/include/asm/uaccess.h
中。
两个函数定义:
static inline int copy_from_user(void *to, const void __user volatile *from, unsigned long n){
__chk_user_ptr(from, n);
volatile_memcpy(to, from, n);
return 0;
}
static inline int copy_to_user(void __user volatile *to, const void *from, unsigned long n){
__chk_user_ptr(to, n);
volatile_memcpy(to, from, n);
return 0;
}
可以看到两个函数均是调用了 _memcpy() 函数:
static void volatile_memcpy(volatile char *to, const volatile char *from, unsigned long n){ while (n--) *(to++) = *(from++); }
其实在这里,我们可以思考,既然拷贝的功能上面的_memcpy()
函数就可以实现,为什么还要封装成 copy_to_user()
和copy_from_user()
呢?
答案是_memcpy()
函数是有缺陷的,譬如我们在用户层调用函数时传入的不是字符串,而是一个不能访问或修改的地址,那样就会造成系统崩溃。
出于上面的原因,内核和用户态之间交互的数据时必须要先对数据进行检测,如果数据是安全的,才可以进行数据交互。上面的函数就是memcpy
的改进版,在memcpy
功能的基础上加上的检查传入参数的功能,防止有些人有意或者无意的传入无效的参数。
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
参数:
返回值:
成功返回0,失败返回没有拷贝成功的数据字节数
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
参数:
返回值:
成功返回0,失败返回没有拷贝成功的数据字节数
Linux中将设备分为三大类:字符设备(I2C、USB、SPI等)、块设备(存储器相关的设备如EMMC、SD卡、U盘等)和网络设备(网络相关的设备WIFI等)。
杂项设备归属于字符设备,每个设备节点都有主设备号和次设备号 ,设备号是识别设备的一种方式,Linux系统中有很多杂项设备,而杂项设备的主设备号固定为10。 使用命令