当前位置: 首页 > news >正文

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

目录

      • file_operation 接口实现
        • open close接口的实现
        • read 接口实现
        • write 接口实现
        • ioctl 接口实现
        • ioctl cmd 值的定义
        • 如何检查命令
        • 内存拷贝
      • 字符设备注册函数
      • cdev 操作相关函数

file_operation 接口实现

open close接口的实现
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
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:待操作的设备文件 struct 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 :ioctlunlocked_ioctlcompat_ioctl 现在只有unlocked_ioctlcompat_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] 最多是 14 bit,表示数据的大小
  • 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 数据结构 自动获取主次设备号
first_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;  
} 
http://www.dtcms.com/a/212699.html

相关文章:

  • 独占内存访问工作原理
  • 题目 3326: 蓝桥杯2025年第十六届省赛真题-最短距离
  • Linux 内核学习(9) --- Linux sysfs 文件系统
  • 驱动: file_operations、register_chrdev、/proc 文件系统概述
  • Android 启动流程开发注意事项
  • 删除链表的倒数第N个结点--LeetCode
  • 5.25本日总结
  • MySQL的查询进阶
  • 中断和信号详解
  • 嵌入式软件--DAY8 IIC通讯下 硬件实现
  • 什么是maven(详细介绍)
  • MMDetection3D最全源码安装教程
  • 量子力学:量子力学为什么不属于经典物理学的范畴?
  • NISP和CISP有什么区别,哪个更好
  • JAVA 关键词
  • Mac系统-最方便的一键环境部署软件ServBay(支持php,java,python,node,go,mysql等)没有之一,已亲自使用!
  • 【LeetCode】大厂面试算法真题回忆(99)--Linux发行版的数量
  • TCP 的四次挥手
  • PTA刷题笔记(难度预警!!!有详解)
  • 再写数的划分(dfs)
  • 第四章 面向对象(基础)
  • SymAgent:一种用于知识图谱复杂推理的神经符号自学Agent框架
  • Linux Kernel调试:强大的printk(三)
  • Markdown 到 LaTeX:Overleaf 学习笔记
  • 基于javaweb的SpringBoot体检管理系统设计与实现(源码+文档+部署讲解)
  • 【C++】unordered_map、unordered_set 的使用
  • CQF预备知识:Python相关库 -- NumPy 基础知识 - ndarray 索引
  • vue3组件--无限滚动效果
  • Android7 Input(九)View 建立Input Pipeline
  • 15 dart类(get,set,静态,继承,抽象,接口,混入)