linux内核等待队列wait_queue_head_t
头文件
include <linux/wait.h>
定义并初始化
wait_queue_head_t r_wait;
init_waitqueue_head(&cm_dev->r_wait);
wait_queue_head_t 表示等待队列头,等待队列wait时,会导致进程或线程被休眠,一个等待队列头中可以有很多的等待队列元素。每个元素绑定一个进程或者线程。这里绑定进程或者线程的目的,是为了在执行wakeup时,知道应该唤醒谁。
Linux 字符设备驱动开发基础——read()、write() 相关函数解析
在Linux
字符设备驱动中,用户程序使用read()
、write()
相关函数时,内核会调用驱动程序中的的file_operations
结构体中对应的read()
、write()
函数。
file_operations
,其是一个函数指针的集合,用于存放我们定义的用于操作设备的函数的指针,如果我们不定义,它默认保留为NULL
。其中有最重要的几个函数,分别是open()
、read()
、write()
、ioctl()
,下面分别对其进行解析。
1. 打开和关闭设备函数
(1)打开设备
int (*open) (struct inode *, struct file *);
在操作设备前必须先调用open
函数打开文件,可以干一些需要的初始化操作。当然,如果不实现这个函数的话,驱动会默认设备的打开永远成功。打开成功时open
返回0。
(2)关闭设备
int (*release) (struct inode *, struct file *);
当设备文件被关闭时内核会调用这个操作,当然这也可以不实现,函数默认为NULL
。关闭设备永远成功。
2. read()、write() 函数
2.1 read() 函数
函数原型:
<span style="color:#000000"><span style="background-color:#282c34"><code class="language-c">ssize_t <span style="color:#999999">(</span><span style="color:#669900">*</span>read<span style="color:#999999">)</span> <span style="color:#999999">(</span><span style="color:#c678dd">struct</span> file <span style="color:#669900">*</span>filp<span style="color:#999999">,</span> <span style="color:#c678dd">char</span> __user <span style="color:#669900">*</span>buffer<span style="color:#999999">,</span> size_t size<span style="color:#999999">,</span> loff_t <span style="color:#669900">*</span>p<span style="color:#999999">)</span><span style="color:#999999">;</span>
</code></span></span>
参数含义:
- filp:待操作的设备文件
file
结构体指针; - buffer:为对应放置所读数据的缓冲区指针(即用户空间内存地址);
- size:为要读取的数据长度;
- p:为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值;
- __user:是一个空的宏,主要用来显示的告诉程序员它修饰的指针变量存放的是用户空间的地址。
返回值:
成功实际读取的字节数,失败返回负值。
如果该操作为空,将使得read系统调用返回负EINVAL失败,正常返回实际读取的字节数。
两个函数的作用分别是 从设备中获取数据及发送数据给设备,应用程序中与之对应的也有 write()
函数及 read()
函数:
<span style="color:#000000"><span style="background-color:#282c34"><code class="language-c">len <span style="color:#669900">=</span> <span style="color:#61aeee">read</span><span style="color:#999999">(</span>fd<span style="color:#999999">,</span>buf<span style="color:#999999">,</span>len <span style="color:#999999">)</span>
<span style="color:#c678dd">static</span> ssize_t <span style="color:#61aeee">hello_read</span><span style="color:#999999">(</span><span style="color:#c678dd">struct</span> file <span style="color:#669900">*</span>filep<span style="color:#999999">,</span> <span style="color:#c678dd">char</span> __user <span style="color:#669900">*</span>buf<span style="color:#999999">,</span> size_t len<span style="color:#999999">,</span> loff_t <span style="color:#669900">*</span>pos<span style="color:#999999">)</span>
</code></span></span>
<span style="color:#000000"><span style="background-color:#282c34"><code class="language-c">len <span style="color:#669900">=</span> <span style="color:#61aeee">write</span><span style="color:#999999">(</span>fd<span style="color:#999999">,</span>buf<span style="color:#999999">,</span>size<span style="color:#999999">)</span>
<span style="color:#c678dd">static</span> ssize_t <span style="color:#61aeee">hello_write</span><span style="color:#999999">(</span><span style="color:#c678dd">struct</span> file <span style="color:#669900">*</span>filep<span style="color:#999999">,</span> <span style="color:#c678dd">const</span> <span style="color:#c678dd">char</span> __user <span style="color:#669900">*</span>buf<span style="color:#999999">,</span> size_t len<span style="color:#999999">,</span> loff_t <span style="color:#669900">*</span>pos<span style="color:#999999">)</span>
</code></span></span>
我们知道,应用程序工作在用户空间,而驱动工作在内核空间,二者不能直接通信的,那我们用何种方法进行通信呢?下面介绍一下内核中的 memcpy—copy_from_user和copy_to_user,虽然说内核中不能使用C
库提供的函数,但是内核也有一个memcpy
函数,用法跟C
库中的一样。
2.3 copy_from_user函数与copy_to_user函数
从字面意义上理解:user是指用户,即用户空间。
在file_operations
结构体中实现的write
函数,即2.2中的write()
函数中,用户空间向内核空间拷贝(写)数据需要使用copy_from_user
函数。而用户空间从内核空间读取数据需要使用copy_to_user
函数。两个函数定义在arch/arm/include/asm/uaccess.h
中。
两个函数定义:
<span style="color:#000000"><span style="background-color:#282c34"><code class="language-c"><span style="color:#c678dd">static</span> <span style="color:#c678dd">inline</span> <span style="color:#c678dd">int</span> <span style="color:#61aeee">copy_from_user</span><span style="color:#999999">(</span><span style="color:#c678dd">void</span> <span style="color:#669900">*</span>to<span style="color:#999999">,</span> <span style="color:#c678dd">const</span> <span style="color:#c678dd">void</span> __user <span style="color:#c678dd">volatile</span> <span style="color:#669900">*</span>from<span style="color:#999999">,</span> <span style="color:#c678dd">unsigned</span> <span style="color:#c678dd">long</span> n<span style="color:#999999">)</span><span style="color:#999999">{</span>
<span style="color:#61aeee">__chk_user_ptr</span><span style="color:#999999">(</span>from<span style="color:#999999">,</span> n<span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#61aeee">volatile_memcpy</span><span style="color:#999999">(</span>to<span style="color:#999999">,</span> from<span style="color:#999999">,</span> n<span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#c678dd">return</span> <span style="color:#98c379">0</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#c678dd">static</span> <span style="color:#c678dd">inline</span> <span style="color:#c678dd">int</span> <span style="color:#61aeee">copy_to_user</span><span style="color:#999999">(</span><span style="color:#c678dd">void</span> __user <span style="color:#c678dd">volatile</span> <span style="color:#669900">*</span>to<span style="color:#999999">,</span> <span style="color:#c678dd">const</span> <span style="color:#c678dd">void</span> <span style="color:#669900">*</span>from<span style="color:#999999">,</span> <span style="color:#c678dd">unsigned</span> <span style="color:#c678dd">long</span> n<span style="color:#999999">)</span><span style="color:#999999">{</span>
<span style="color:#61aeee">__chk_user_ptr</span><span style="color:#999999">(</span>to<span style="color:#999999">,</span> n<span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#61aeee">volatile_memcpy</span><span style="color:#999999">(</span>to<span style="color:#999999">,</span> from<span style="color:#999999">,</span> n<span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#c678dd">return</span> <span style="color:#98c379">0</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
</code></span></span>
可以看到两个函数均是调用了 _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)
参数:
- to:目标地址(内核空间)
- from:源地址(用户空间)
- n:将要拷贝数据的字节数
返回值:
成功返回0,失败返回没有拷贝成功的数据字节数
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
参数:
- to:目标地址(用户空间)
- from:源地址(内核空间)
- n:将要拷贝数据的字节数
返回值:
成功返回0,失败返回没有拷贝成功的数据字节数
Linux中将设备分为三大类:字符设备(I2C、USB、SPI等)、块设备(存储器相关的设备如EMMC、SD卡、U盘等)和网络设备(网络相关的设备WIFI等)。
杂项设备归属于字符设备,每个设备节点都有主设备号和次设备号 ,设备号是识别设备的一种方式,Linux系统中有很多杂项设备,而杂项设备的主设备号固定为10。 使用命令<cat /proc/misc>可以查看各杂项设备。