基于字符设备驱动的共享内存实现

思路来源—sys文件系统

sys文件系统的设计思想:

将内核的一些信息,以文件的形式列在sys目录下。应用层通过读写该目录下的文件,可以和内核进行一些信息交互。

由此设计跨设备的内存共享,实现用户态和内核态的信息交互。

设备类:

  • /sys/class,内核将驱动进行分类,比如和声音有关的、和时钟有关的;
  • class_creat() 创建的;
  • /sys/class/xxx/ 目录下有dev,event,power,subsystem等文件,例如cat dev,得知dev中内容是设备号;例如event是内核给udev传递的信息的地方。

字符设备驱动的创建

A. 字符设备创建

1.**创建设备号 **

dev_t是一个32位的整数,其中的12位用来标示主设备号,其余的20位用来标示次设备号;

#include <linux/types.h>
dev_t dev_id;

拓展:

我们可以使用两个宏来获得设备的主设备号及次设备号,其中,major为主设备号,minor为次设备号。

MAJOR(dev_t dev_id);
MINOR(dev_t dev_id);

将主设备号和次设备号转换为dev_t类型,则可以使用下面的宏:

MKDEV(int major, int minor);
2.注册设备

静态注册设备号, 用于已知设备号的情况

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

参数:

from为要分配设备号的起始值,count是所请求的连续设备号的个数,name为设备名称。

分配成功时返回值为0,否则返回错误编号

动态分配设备号,用于未知设备号的情况

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

参数:

dev用于保存申请成功后的第一个设备号,baseminor第一个要使用的次设备号,常常为0,另外参数同上。该函数可以自动避开设备号重复的冲突,会使用一个未使用的设备号。返回值:小于0,则错误,自动分配设备号错误。否则分配得到的设备号就被第一个参数带出来。

使用动态注册方法,注册设备

alloc_chrdev_region(&dev_id, 0, 1, "dev_demo");
3.申请字符设备结构体

结构体原型:

struct cdev {
    struct kobject kobj;  //内嵌的 kobject 对象
    struct module *owner;    //所属模块
    const struct file_operations *ops;//文件操作结构体
    struct list_head list;
    dev_t dev;    //设备号
    unsigned int count;
};

申请字符设备结构体实现:

#include <linux/cdev.h>
struct cdev my_chrdev = cdev_alloc() ;
4.定义一个文件操作集

文件操作集原型:

struct file_operations {
    struct module *owner;
    /* 拥有该结构的模块的指针,一般为 THIS_MODULES */
    loff_t(*llseek)(struct file *, loff_t, int);
    /* 用来修改文件当前的读写位置 */
    ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*);
    /* 从设备中同步读取数据 */
    ssize_t(*write)(struct file *, const char _ _user *, size_t, loff_t*);
    /* 向设备发送数据*/
    ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);
    /* 初始化一个异步的读取操作*/
    ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t);
    /* 初始化一个异步的写入操作*/
     int(*readdir)(struct file *, void *, filldir_t);
    /* 仅用于读取目录,对于设备文件,该字段为 NULL */
    unsigned int(*poll)(struct file *, struct poll_table_struct*);
    
    /* 轮询函数,判断目前是否可以进行非阻塞的读取或写入*/
    int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);
    /* 执行设备 I/O 控制命令*/
    long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
    /* 不使用 BLK 的文件系统,将使用此种函数指针代替 ioctl */
    long(*compat_ioctl)(struct file *, unsigned int, unsigned long);
    /* 在 64 位系统上, 32 位的 ioctl 调用,将使用此函数指针代替*/
    int(*mmap)(struct file *, struct vm_area_struct*);
    /* 用于请求将设备内存映射到进程地址空间*/
    int(*open)(struct inode *, struct file*);
    /* 打开 */
    int(*flush)(struct file*);
    int(*release)(struct inode *, struct file*);
    /* 关闭*/
    int (*fsync) (struct file *, struct dentry *, int datasync);
    /* 刷新待处理的数据*/
    int(*aio_fsync)(struct kiocb *, int datasync);
    /* 异步 fsync */
    int(*fasync)(int, struct file *, int);
    /* 通知设备 FASYNC 标志发生变化*/
    int(*lock)(struct file *, int, struct file_lock*);
    ssize_t(*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int);
    /* 通常为 NULL */
    unsigned long(*get_unmapped_area)(struct file *,unsigned long, nsigned long,
    unsigned long, unsigned long);
    /* 在当前进程地址空间找到一个未映射的内存段 */
    int(*check_flags)(int);
    /* 允许模块检查传递给 fcntl(F_SETEL...)调用的标志 */
    int(*dir_notify)(struct file *filp, unsigned long arg);
    /* 对文件系统有效,驱动程序不必实现*/
    int(*flock)(struct file *, int, struct file_lock*);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t,
    unsigned int); /* 由 VFS 调用,将管道数据粘接到文件 */
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t,
    unsigned int); /* 由 VFS 调用,将文件数据粘接到管道 */
    int (*setlease)(struct file *, long, struct file_lock **);
    };

llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。
read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。
write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行 write()系统调用时,将得到-EINVAL 返回值。
readdir()函数仅用于目录,设备节点不需要实现它。
ioctl()提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,返回给调用程序一个非负值。
mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进行 mmap()系统调用时将获得-ENODEV 返回值。这个函数对于帧缓冲等设备特别有意义。
当用户空间调用 Linux API 函数 open()打开设备文件时,设备驱动的 open()函数最终被调用。
驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。与 open()函数对应的是 release()函数。
poll()函数一般用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发时,用户空间进行 select()和 poll()系统调用将引起进程的阻塞。
aio_read()和 aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。设备实现这两个函数后,用户空间可以对该设备文件描述符调用 aio_read()、 aio_write()等系统调用进行读写。

文件操作结构体声明及初始化,需要对函数进行实现

 int my_chrdev_open(struct inode *inode, struct file *filp)
 {
  printk("enter my_chrdev_open!\n");
  return 0;
 }

 int my_chrdev_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long data)
 {
  printk("enter my_chrdev_ioctl!\n");
  return 0;
 }
 struct file_operations my_chrdev_fops = 
 {
  .owner = THIS_MODULE,
  .open = my_chrdev_open,
  .read = my_chrdev_read,
  .write = my_chrdev_write,
  .ioctl = my_chrdev_ioctl,
  .release = my_chrdev_release,
 };

B. 字符设备注册到内核

5.字符设备初始化

函数原型:

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

实现:

my_chrdev.ops = &my_chrdev_fops;
cdev_init(&my_chrdev, &my_chrdev_fops);
6.将字符设备加入到内核

函数原型:

 int cdev_add(struct cdev *p, dev_t dev, unsigned count)

实现:

cdev_add(&my_chrdev, dev_id, 1);

C. 注册设备节点

内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,在/sys/class/下创建类目录,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。

#include <linux/device.h>
7.创建设备类

函数原型:

struct class *class_create(struct module *owner, const char *name)

实现:

struct class *my_chrdev_class = NULL;
my_chrdev_class = class_create(THIS_MODULE, "my_chrdev_class");

会在/sys/class目录下创建名为"my_chrdev_class"的文件夹

8.创建设备文件

函数原型:

struct device *device_create(struct class *class, struct device *parent, dev_t devt,void drvdata, const char *fmt,...)

class:类 – 在文件系统中用来管理设备节点文件的类

parent:当前设备的父设备 – (无:NULL)

devt:设备号 – 创建设备节点文件所关联的设备号

drvdata:当前设备驱动的私有数据 – 任何数据(无:NULL)

const charfmt, … :设备节点文件名(格式化字符串传参)

实现:

struct device *my_chrdev_device = NULL;
my_chrdev_device = device_create(my_chrdev_class, NULL, dev_id, NULL, "my_chrdev_device");

会在/dev目录下创建创建名为"my_chrdev_device"的文件夹

D. 注销相关字符设备

9.注销一个设备号

函数原型:

void unregister_chrdev_region(dev_t from, unsigned count)
10.**注销一个字符设备 **

函数原型:

void cdev_del(struct cdev *p)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值