嵌入式学习——字符设备驱动

1、字符设备驱动框架

1.1、字符设备

定义:只能以一个字节一个字节的方式读写的设备,不能随机的读取设备中的某一段数据,读取数据需要按照先后顺序。(字符设备是面向字节流的)

常见的字符设备:鼠标 键盘 串口 控制台

块设备:可以从设备的任意位置读取一定长度数据的设备。

常见的块设备:硬盘 磁盘 光盘 U盘 SD卡 TF卡

1.2、字符设备驱动框架

init流程:–> HelloModule

  1. 申请设备号(静态申请 动态申请)
  2. 创建一个字符设备
  3. 初始化字符设备
  4. 将设备号和字符设备关联起来

exit流程:–> HelloExit

  1. 删除字符设备
  2. 删除设备号
1.2.1 设备号

定义:设备号是设备在内核中的身份和标识,是内核区分不同设备的唯一信息,设备号是由主设备号和次设备号构成,主设备号表示一类设备,次设备号表示该类设备中的某一个设备。

设备号:是一个32bit的无符号整数,高12bit是主设备号,低20bit是次设备号。

内核函数和库函数的区别:库函数是语言本身的一部分,而系统函数是内核提供给应用程序的接口,属于系统的一部分。

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)	// 前20位置1
/*
 * 1 << 20 -1 : 1 0000 0000 0000 0000 0000 - 1
 *            : 0 1111 1111 1111 1111 1111
 */

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))	// 获取主设备号
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))	// 获取次设备号
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))	// 合成总设备号
1.2.2 申请设备号

申请设备号的两种方式:
静态申请

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

    作用:静态申请设备号
    from:设备号(由主次设备号构成)
    count:子设备个数
    *name:设备名称
    返回值:0–>成功 非0–>失败

  • void unregister_chrdev_region(dev_t from, unsigned count)

    作用:从内核中移除设备号
    from:设备号(由主次设备号构成)
    count:子设备个数

动态申请

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

    作用:动态申请设备号
    *dev:指向设备号的指针
    baseminor:子设备的第一个编号
    count:子设备个数
    *name:设备名称
    返回值:0–>成功 非0–>失败

1.2.3 创建字符设备
  • struct cdev *cdev_alloc(void)

    作用:创建一块用于存放字符设备的空间
    返回值:是指向创建成功的字符设备的指针

在Linux内核中用struct cdev 来描述一个字符设备

THIS_MODULE --> 当前模块本身

  • void cdev_del(struct cdev *p)

    作用:删除字符设备
    *p:指向字符设备的指针

1.2.4 初始化字符设备 --> 绑定驱动方法
  • void cdev_init(struct cdev *cdev, const struct file_operations *fops)

    作用:初始化字符设备
    *cdev:指向字符设备的指针
    *fope:指向操作字符设备的函数集的指针

1.2.5 字符设备与设备号关联
  • int cdev_add(struct cdev *p, dev_t dev, unsigned count)

    作用:将字符设备和设备号关联,并将字符设备添加到内核中
    *p:指向字符设备的指针
    dev:设备号
    count:子设备的个数
    返回值:成功为0 失败非0

退出函数
  1. 删除设备 cdev_del
  2. 删除设备号unregister_chrdev_region
测试步骤
  1. sudo insmod hello.ko

  2. dmesg |tail -->250 0

  3. cat /proc/devices–>查看设备号 250 0

  4. sudo mknod /dev/haha0 c 250 0

  5. ls -l /dev/haha*—>查看创建字符设备文件

  6. sudo ./test–>open hahao ok!

  7. dmesg |tail–>helloopen/helloClose

  8. sudo rmmod hello.ko

  9. sudo rm /dev/haha0

区分字符设备驱动框架中使用的s三个结构体:

struct file:代表内核中一个打开的文件。系统中每个打开的文件在内核中都有关联的struct file

struct inode:用来记录文件在物理上的位置信息。它和打开文件的struct file结构不同,一个文件可以对应多个struct file,但是只有一个struct inode

struct file_operations:是内核给用户提供的驱动接口函数集,用户可以定义函数集中的任何驱动方法。(对于不支持的一般不写)

2、实现用户空间和内核空间的数据拷贝

用户代码对字符设备的任何操作,最终都要落实到设备对应的底层操作函数上

  • 内核空间 --> 用户空间 read --> HelloRead
  • 用户空间 --> 内核空间 write --> HelloWrite

应用层:

fd = open("/dev/haha0");
read(fd,)
close(fd)

驱动层:
增加HelloRead

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

char g_buf[]="hello test---";

ssize_t HelloRead (struct file *pFile, char __user *buf, size_t count, loff_t *p)
{
	copy_to_user(buf, g_buf, count);
}

  • unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

    作用:从内核空间拷贝数据到用户空间
    *to:用户空间指针
    *from:内核空间指针(数据源)
    n:拷贝的字节数

  • unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)

    作用:从用户空间拷贝数据到内核空间
    *to:用户空间指针
    *from:内核空间指针(数据源)
    n:拷贝的字节数

  • unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

测试步骤

  1. sudo insmod hello.ko
  2. dmesg |tail
  3. sudo mknod /dev/haha0 c 250 0
  4. ls -l /dev/hah*
  5. sudo ./test–>查看是否读到数据?
  6. sudo rmmod hello
  7. sudo rm /dev/haha0
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值