头文件
? 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()
?相关函数时,内核会调用驱动程序中的的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
。关闭设备永远成功。
函数原型:
<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>
参数含义:
file
结构体指针;返回值:
成功实际读取的字节数,失败返回负值。
如果该操作为空,将使得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
库中的一样。
从字面意义上理解: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)
?
参数:
返回值:
成功返回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。 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 使用命令<cat /proc/misc>可以查看各杂项设备。