前言
这篇文章是通过对一个简单字符设备驱动的操作来解释,用户态的读写操作是怎么映射到具体设备的。
因为针对不同版本的linux内核,驱动的接口函数一直有变化,这贴出我测试的系统信息:
root@ubuntu:~/share/dev/cdev-2# cat /etc/os-release |grep -i ver
VERSION="16.04.5 LTS (Xenial Xerus)"
VERSION_ID="16.04"
VERSION_CODENAME=xenial
root@ubuntu:~/share/dev/cdev-2#
root@ubuntu:~/share/dev/cdev-2# uname -r
4.15.0-33-generic
字符驱动
这里给出了一个不怎么标准的驱动,定义了一个结构体 struct dev,其中buffer成员模拟驱动的寄存器。由wr,rd作为读写指针,len作为缓存buffer的长度。具体步骤如下:
1. 定义 init 函数,exit函数,这是在 insmod,rmmod时候调用的。
2. 定义驱动打开函数open,这是在用户态打开设备时候调用的。
3. 定义release函数,这是在用户态关闭设备时候用到的。
4. 定义read,write,poll函数,并挂接到 file_operations结构体中,所有用户态的read,write,poll都会最终调到这些函数。
chardev.c
/*
参考:深入浅出linux设备驱动开发
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/semaphore.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/poll.h>
#define MAXNUM 100
#define MAJOR_NUM 400 //主设备号 ,没有被使用
struct dev{
struct cdev devm; //字符设备
struct semaphore sem;
int flag;
poll_table* table;
wait_queue_head_t outq;//等待队列,实现阻塞操作
char buffer[MAXNUM+1]; //字符缓冲区
char *rd,*wr,*end; //读,写,尾指针
}globalvar;
static struct class *my_class;
int major=MAJOR_NUM;
static ssize_t globalvar_read(struct file *,char *,size_t ,loff_t *);
static ssize_t globalvar_write(struct file *,const char *,size_t ,loff_t *);
static int globalvar_open(struct inode *inode,struct file *filp);
static int globalvar_release(struct inode *inode,struct file *filp);
static unsigned int globalvar_poll(struct file* filp, poll_table* wait);
/*
结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。
该结构体的每个域都对应着驱动内核模块用来处理某个被请求的事务的函数的地址。
设备"gobalvar"的基本入口点结构变量gobalvar_fops
*/
struct file_operations globalvar_fops =
{
/*
标记化的初始化格式这种格式允许用名字对这类结构的字段进行初始化,这就避免了因数据结构发生变化而带来的麻烦。
这种标记化的初始化处理并不是标准 C 的规范,而是对 GUN 编译器的一种(有用的)特殊扩展
*/
//用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数