Linux 内核学习(5)---- 字符设备驱动操作函数

file_operation 接口实现

open close接口的实现
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);

open release 操作接口比较简单

read 接口实现
ssize_t (*read) (struct file * filp, char __user *buf, size_t count, loff_t * f_pos);

struct file * filp:打开设备节点分配的 struct file 类型
char __user * buf: 待写入所读取数据的用户空间缓冲区指针
size_t count:待读取数据字节数
loff_t f_pos:待读取数据文件位置,读取完成后根据实际读取字节数重新定位

__user :是一个空的宏,主要用来显示的告诉程序员它修饰的指针变量存放的是用户空间的地址
如果该操作为空,将使得read系统调用返回负EINVAL失败,正常返回实际读取的字节数

write 接口实现
ssize_t (*write) (struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);

struct file *filp: 待操作的设备文件file结构体指针
const char __user buf: 待写入所读取数据的用户空间缓冲区指针
size_t count:待写入数据字节数
loff_t * f_pos:待写入数据文件位置,读取完成后根据实际读取字节数重新定位

ioctl 接口实现
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)
long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);

kernel 2.6.35 及之前的版本中struct file_operations 一共有3个ioctl :ioctl,unlocked_ioctl和compat_ioctl 现在只有unlocked_ioctl和compat_ioctl 了
在kernel 2.6.36 中已经完全删除了struct file_operations 中的ioctl 函数指针,取而代之的是unlocked_ioctl
应用层调用 ioctl 的方式

int ioctl(int fd, int cmd, ...);
参数:
fd:打开设备文件的时候获得文件描述符 
cmd:第二个参数:给驱动层传递的命令,需要注意的时候,驱动层的命令和应用层的命令一定要统一
第三个参数: "..."在C语言中,很多时候都被理解成可变参数。

当我们通过ioctl调用驱动层xxx_ioctl的时候,有三种情况可供选择:

  1. 不传递数据给ioctl
  2. 传递数据给ioctl,希望它最终能把数据写入设备(例如:设置Framebuffer 的显示信息)
  3. 调用_ioctl希望获取设备的硬件参数(例如:获取当前串口设备的波特率)
    这三种情况中,有时候需要从用户空间读取数据,有时候需要从内核空间拷贝数据,有时候不需要传递数据,
    用"..."来表示,可以带一个参数,或者不带参数
ioctl cmd 值的定义

include/uapi/asm-generic/ioctl.h


ioctl_cmd.jpg
#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \ //30
     ((type) << _IOC_TYPESHIFT) | \ //8
     ((nr)   << _IOC_NRSHIFT) | \ //0
     ((size) << _IOC_SIZESHIFT)) //16

#ifndef __KERNEL__
#define _IOC_TYPECHECK(t) (sizeof(t))
#endif

/* used to create numbers */
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOR_BAD(type,nr,size)  _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

/* used to decode ioctl numbers.. */
#define _IOC_DIR(nr)        (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr)       (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr)     (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr)       (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

type:一般为magincNumber nr:一般为递增的序列号 size:传输数据的size
cmd 是一个unsigned int 类型的整形数,大小为32Bit

  • cmd 的高位 2Bit cmd[31:30] 是数据的传输方向,可以是 _IOC_READ,_IOC_WRITE或者IOC_READ|_IOC_WRITE和_IOC_NONE
  • cmd[29:16] 最多是14bit,表示数据的大小
  • cmd[15:8] 命令的类型,一般使用一个固定的数值,称为 magicnumber
  • cmd[7:0] 序列号,一般表示第几个命令,随着命令数递增

通过 linux 中提供的宏,我们只需要定义 MagicNumber,命令序号和数据字段(宏中会自动使用sizeof)就好了

如何检查命令

_IOC_TYPE(nr) 来判断应用程序传下来的命令type是否正确
_IOC_DIR(nr)来得到命令是读还是写,然后再通过宏access_ok(type,addr,size)来判断用户层传递的内存地址是否合法

内核空间和用户空间的内存拷贝

include/asm-generic/uaccess.h

static inline int copy_from_user(void *to, const void __user volatile *from,unsigned long n)
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
 /* to:目标地址(用户空间)
  from:源地址(内核空间)
  n:将要拷贝数据的字节数*/

#define put_user(x, ptr)                    \
({                              \
    void *__p = (ptr);                  \
    might_fault();                      \
    access_ok(VERIFY_WRITE, __p, sizeof(*ptr)) ?        \
        __put_user((x), ((__typeof__(*(ptr)) *)__p)) :  \
        -EFAULT;                    \
})
/*
  data:可以是字节、半字、字、双字类型的内核变量
  ptr:用户空间内存指针
*/

#define get_user(x, ptr)                    \
({                              \
    const void *__p = (ptr);                \
    might_fault();                      \
    access_ok(VERIFY_READ, __p, sizeof(*ptr)) ?     \
        __get_user((x), (__typeof__(*(ptr)) *)__p) :    \
        ((x) = (__typeof__(*(ptr)))0,-EFAULT);      \
})
/*
  data:可以是字节、半字、字、双字类型的内核变量
  ptr:用户空间内存指针
*/

Linux 提供 copy_from_user,copy_to_user,put_user 和 get_user宏来和用户空间交换数据
使用这些函数和用户空间交互时,内核会做参数检查,比如指针指向的区域是否属于用户空间,是否属于用户的当前进程,读和写的内存必须由相应的权限

字符设备注册函数

内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()
代码位置: include/linux/fs.h kernel/fs/char_dev.c

static inline int register_chrdev(unsigned int major, const char *name,
                  const struct file_operations *fops)
{
    return __register_chrdev(major, 0, 256, name, fops);
}

static inline void unregister_chrdev(unsigned int major, const char *name)
{
    __unregister_chrdev(major, 0, 256, name);
}

int register_chrdev_region(dev_t from, unsigned count, const char *name)

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

int  unregister_chrdev_region(dev_t, unsigned);

register_chrdev 可以直接传入 file_opeations 结构体,本质上相当于将 cdev 的操作在函数内部实现了
register_chrdev_region 可以指定主设备号,但需要配合cdev 结构体一起使用
alloc_chrdev_region 动态分配主设备号,传出dev_t 的结构

register_chrdev_region(dev_t first,unsigned int count,char *name)

First: 主设备号,要分配的设备编号范围的初始值, 这组连续设备号的起始设备号
Count: 连续编号范围. 是这组设备号的大小(也是次设备号的个数)
Name: 编号相关联的设备名称. (/proc/devices);本组设备的驱动名
正确的时候返回 0

register_chrdev_region 需要和 cdev 的相关函数一起使用

int alloc_chrdev_region(dev_t* dev ,unsigned int first minor,unsigned int count,char *name)

dev_t* dev 是传出的参数 用于获取 dev_t 数据结构 自动获取主次设备号
minor 次设备号
count 请求连续分配的设备个数
name 出现在 /proc/devices 和 sysfs 中

cdev 操作相关函数

include/linux/cdev.h
用于分配,初始化一个cdev 结构,将struct file_operations赋值给它,最后将它和 dev_t 结构绑定

struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};

void cdev_init(struct cdev *, const struct file_operations *);

struct cdev *cdev_alloc(void);

void cdev_put(struct cdev *p);

int cdev_add(struct cdev *, dev_t, unsigned);

void cdev_set_parent(struct cdev *p, struct kobject *kobj);
int cdev_device_add(struct cdev *cdev, struct device *dev);

一般用法如下:

demo_cdev = cdev_alloc();  
cdev_init(demo_cdev,&Mstar_demo_driver);  
demo_cdev->owner = THIS_MODULE;  

ret = cdev_add(demo_cdev,demo_devt,1);  
if(ret) 
{  
    printk("cdev create error!\n");  
    unregister_chrdev_region(demo_devt,1);  
    return ret;  
} 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容