找回密码
 立即注册
首页 业界区 业界 linux字符设备驱动

linux字符设备驱动

忆雏闲 6 天前
1.字符设备驱动相关系统函数简介

1.1 container_of
  1. /**
  2. * container_of 通过一个结构体成员的指针,获取包含该成员的结构体的起始地址
  3. * @ptr:        变量的指针
  4. * @type:       指针指向的结构体类型
  5. * @member:     结构体中的变量类型
  6. *
  7. */
  8. #define container_of(ptr, type, member) ({              \
  9.     void *__mptr = (void *)(ptr);                   \
  10.     BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&   \
  11.                      !__same_type(*(ptr), void),            \
  12.                      "pointer type mismatch in container_of"); \
  13.     ((type *)(__mptr - offsetof(type, member))); })
复制代码
为了简化理解,可以将container_of函数理解为如下表示
  1. #define container_of(ptr, type, member) \
  2.     ((type *)((char *)(ptr) - offsetof(type, member)))
复制代码
该函数根据 结构体成员变量的指针,通过该变量相对于结构体的偏移,得到了该变量对应的结构体的指针。
这是一个非常灵活的用法,通过保存某个结构体变量的指针,可以通过该指针反向推出该结构体的指针。在后续案例中有详细解释。
1.2 register_chrdev_region

register_chrdev_region 是 Linux 内核中用于注册字符设备编号范围的函数。该函数为驱动程序预留一段连续的设备号(主设备号 + 起始次设备号),后续将字符设备(通过 cdev_add)绑定到这些设备号上,函数原型如下。
  1. /**
  2. * first:dev_t 类型,指定要注册的起始设备号。使用 MKDEV(major, minor) 宏生成
  3. * count:需要注册的连续设备号数量(次设备号的范围)
  4. * name:设备的名称,该名称会出现在 /proc/devices 文件中,用于标识该组设备号属于哪个驱动。
  5. * 返回值: 成功返回0
  6. *        参数无效返回     -EINVAL
  7. *        设备号被占用返回 -EBUSY
  8. */
  9. int register_chrdev_region(dev_t first, unsigned int count, const char *name);
复制代码
1.3 cdev_init

struct cdev 是内核表示字符设备的对象,每个字符设备驱动都需要创建cdev 实例,并将其注册到内核
cdev_init 负责设置 cdev 的基本字段,为后续的 cdev_add 做准备
  1. /*
  2. * dev:指向要初始化的 struct cdev 结构体的指针。
  3. *       该结构体可以由驱动静态分配 (kmalloc) ,也可以动态分配 (cdev_alloc)
  4. * fops:指向 struct file_operations 结构体的指针
  5. *       该结构体包含了设备支持的各种操作函数(如 open、read、write、ioctl 等)
  6. */
  7. void cdev_init(struct cdev *cdev, const struct file_operations *fops)
  8. {
  9.     memset(cdev, 0, sizeof *cdev);
  10.     INIT_LIST_HEAD(&cdev->list);
  11.     kobject_init(&cdev->kobj, &ktype_cdev_default);
  12.     cdev->ops = fops;
  13. }
复制代码
1.4 cdev_add

该函数建立了设备号与字符设备驱动的关联,使 VFS(虚拟文件系统)能够通过设备号找到对应的驱动程序,从而响应用户空间的打开、读写等操作

  • 将 struct cdev 对象与设备号绑定,在内核的字符设备映射表中建立关联
  • 将 cdev 对象加入内核的全局字符设备链表或哈希表,使 VFS 能够根据设备号找到对应的 cdev
  • 激活设备:调用 cdev_add 后,该设备便可以被用户空间访问(需要使用mknod关联到/dev/mydevname)
  1. /*
  2. * p:指向 cdev_init() 已经初始化的 struct cdev 对象的指针。
  3. * dev:分配给该设备的起始设备号,应该与 register_chrdev_region() 向内核申请的设备号位置一致
  4. * count:与该设备关联的次设备号数量
  5. * 返回值:成功 0
  6.           参数无效    -EINVAL
  7.           设备号被占用 -EBUSY (理论上在分配设备号时已经检查过,但这里仍可能发生,如动态添加时竞争)
  8.           内存不足    -ENOMEM
  9. */
  10. int cdev_add(struct cdev *p, dev_t dev, unsigned count);
复制代码
2. 使用字符设备驱动实现共享内存

下面是使用字符设备驱动实现共享内存的案例,用来快速熟悉驱动函数的使用方法,代码参考书籍为Linux设备驱动详解
  1. #include <linux/init.h>
  2. #include <linux/errno.h>
  3. #include <linux/mm.h>
  4. #include <linux/sched.h>
  5. #include <linux/module.h>
  6. #include <linux/ioctl.h>
  7. #include <linux/io.h>
  8. #include <linux/fs.h>
  9. #include <linux/cdev.h>
  10. #include <linux/uaccess.h>
  11. #include <linux/slab.h>
  12. <h1 id="define-globalmem_size1024">define GLOBALMEM_SIZE        1024</h1>
  13. <h1 id="define-globalmem_magicm">define GLOBALMEM_MAGIC        'M'</h1>
  14. <h1 id="define-mem_clear_ioglobalmem_magic-0">define MEM_CLEAR                _IO(GLOBALMEM_MAGIC, 0)</h1>
  15. <p>struct globalmem_dev
  16. {
  17. // 字符设备
  18. struct cdev m_cdev;
  19. // 共享内存
  20. unsigned char mem[GLOBALMEM_SIZE];
  21. };</p>
  22. <p>static int globalmem_major = 266;</p>
  23. <p>// 存放两个字符设备私有数据
  24. struct globalmem_dev* globalmem_devp;</p>
  25. <p>/* user open fd <em>/
  26. static int globalmem_open(struct inode</em> inode, struct file* filp) {</p>
  27. [code]struct globalmem_dev* dev;
  28. /* 下面是一种常用的区分次设备号的方法
  29. * 通过 globalmem_init 初始化时传入不同的 cdev 指针实现区分 */
  30. // inode 中保存的 i_cdev 指针是 globalmem_init 函数中传入的,globalmem_dev 结构体变量 m_cdev 的指针
  31. // 所以通过 inode->i_cdev 指针,即 m_cdev 成员变量的指针,可以找到其对应的结构体指针
  32. dev = container_of(inode->i_cdev, struct globalmem_dev, m_cdev);
  33. // 将设备结构体指针传给文件私有数据指针,提供给其他函数调用时使用
  34. filp->private_data = dev;
  35. return 0;
复制代码
}
/* user release fd/
static int globalmem_release(struct inode
inode, struct file* filp) {
return 0;
}
/* user read fd /
static ssize_t globalmem_read(struct file
filp, char __user* buf, size_t count, loff_t* ppos) {
unsigned long p = ppos;
// 获得设备结构体的指针(这是通过open函数传入的指针,实现不同次设备使用不同的共享内存)
struct globalmem_dev
dev = filp->private_data;
  1. if(p >= GLOBALMEM_SIZE)
  2.         return 0;
  3. if(count > GLOBALMEM_SIZE - p)
  4.         count = GLOBALMEM_SIZE - p;
  5. copy_to_user(buf, (void*)(dev->mem + p), count);
  6. *ppos = p + count;
  7. return count;
复制代码
}
/* user write fd /
static ssize_t globalmem_write(struct file
filp, const char __user* buf, size_t count, loff_t* ppos) {
unsigned long p = *ppos;
  1. // 获得设备结构体的指针
  2. struct globalmem_dev* dev = filp->private_data;       
  3. if(p >= GLOBALMEM_SIZE)
  4.         return 0;
  5. if(count > GLOBALMEM_SIZE - p)
  6.         count = GLOBALMEM_SIZE - p;
  7. copy_from_user(dev->mem + p, buf, count);
  8. *ppos = p + count;
  9. return count;
复制代码
}
/* user lseek fd /
static loff_t globalmem_llseek(struct file
filp, loff_t offset, int orig) {
loff_t ret;
switch(orig) {
// 从起始位置开始移动指针
case 0:
if(offset < 0) {
ret = -EINVAL;
break;
}
if((unsigned int)offset > GLOBALMEM_SIZE) {
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
// 从当前位置开始移动指针
case 1:
if((filp->f_pos + offset) > GLOBALMEM_SIZE) {
ret = -EINVAL;
break;
}
if((filp->f_pos + offset) < 0) {
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
}
return ret;
}
/* user ioctl fd /
static int globalmem_ioctl(struct inode
inodep, struct file* filp, unsigned int cmd, unsigned long arg) {
// 获取设备结构体指针       
struct globalmem_dev* dev = filp->private_data;
  1. switch(cmd) {
  2. case MEM_CLEAR:
  3.         memset(dev->mem, 0, GLOBALMEM_SIZE);
  4.         break;
  5. default:
  6.         return -EINVAL;
  7. }
  8. return 0;
复制代码
}
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.open = globalmem_open,
.release = globalmem_release,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_ioctl
};
/* 设备驱动模块insmod加载函数 */
static int globalmem_init(void) {
// 向 Linux 内核中注册字符设备编号范围
register_chrdev_region(MKDEV(globalmem_major, 0), 2, "globalmem");
  1. // 为2个次设备以及共享内存分配内存
  2. globalmem_devp = kmalloc(2 * sizeof(struct globalmem_dev), GFP_KERNEL);
  3. memset(globalmem_devp, 0, 2 * sizeof(struct globalmem_dev));
  4. // 初始化字符设备0的基本字段
  5. cdev_init(&(globalmem_devp[0].m_cdev), &globalmem_fops);
  6. globalmem_devp[0].m_cdev.owner = THIS_MODULE;
  7. // 将主设备号globalmem_major次设备号0,与字符设备驱动的关联
  8. cdev_add(&(globalmem_devp[0].m_cdev), MKDEV(globalmem_major, 0), 1);
  9. // 初始化字符设备1的基本字段
  10. cdev_init(&(globalmem_devp[1].m_cdev), &globalmem_fops);
  11. globalmem_devp[1].m_cdev.owner = THIS_MODULE;
  12. // 将主设备号globalmem_major次设备号1,与字符设备驱动的关联
  13. cdev_add(&(globalmem_devp[1].m_cdev), MKDEV(globalmem_major, 1), 1);
  14. return 0;
复制代码
}
static int globalmem_exit(void) {
// 注销cdev
cdev_del(&(globalmem_devp[0].m_cdev));
cdev_del(&(globalmem_devp[1].m_cdev));
// 释放设备结构体内存
kfree(globalmem_devp);       
// 释放设备号
dev_t devno = MKDEV(globalmem_major, 0);
unregister_chrdev_region(devno, 2);
}
MODULE_AUTHOR("cear");
MODULE_LICENSE("GPL");
module_param(globalmem_major, int, S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);
[/code]
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册