================================================================
《Linux驱动 编译一个hello模块》
《Linux驱动 实现一个字符设备驱动》
《Liunx驱动 实现一个misc设备驱动》
================================================================
字符设备基础知识
写一个简单的linux字符设备驱动程序,需要内核里的以下几个头文件,因为需要调用一些基本的宏和一些基本的函数来使用
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
进入Linux内核源码,进入include/linux/
,打开cdev.h
struct cdev {
struct kobject kobj;//设备模型相关的
struct module *owner;//所属于哪个模块--->THIS MODULE
//利用file_operations跟用户态进行操作--->有open , read , write 等方法
const struct file_operations *ops;
struct list_head list;//链表,将设备插入到一条链表里去
dev_t dev;/通过设备号匹配对应的驱动
unsigned int count;//要注册字符设备的个数
} __randomize_layout;
里面还有部分函数,我们暂时只需要cdev_init,cdev_add,cdev_del
打开kdev_t.h
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
//从设备号中取出主设备号
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
//从设备号中取出次设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
//创建一个设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
创建设备号就需要kdev_t这个宏,创建设备号后还要对设备进行注册,这时候需要fs.h这个头文件里的函数,注册和释放
extern int register_chrdev_region(dev_t, unsigned, const char *);
//动态分配设备号,由内核给我们分配一个设备号,这个设备号是内核自动分配的,就不需要我们去使用MKDEV这个宏来进行手动
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
extern void unregister_chrdev_region(dev_t, unsigned);
编写简单的字符设备需要以下步骤:
- 创建设备号
- 注册设备号
- 退出驱动时,注销设备
创建设备文件,利用cat /proc/devices可以查看申请到的设备名、设备号,这里贴出部分代码:
//创建一个字符设备
struct char_dev
{
struct cdev c_dev;
dev_t dev_no;
char buf[1024];
};
struct char_dev *my_dev;
static int __init cdev_test_init(void)
{
int ret;
//创建设备号->主设备号,次设备号
//dev_no = MKDEV(222,2);
//注册设备号
//ret = register_chrdev_region(dev_no,1,"my_dev");
//1.给字符设备分配内存空间
my_dev = kmalloc(sizeof(*my_dev),GFP_KERNEL);
//2.自动申请设备号并注册字符设备
ret = alloc_chrdev_region(&my_dev->dev_no,1,1,"my_dev");
//3.初始化字符设备
cdev_init(&my_dev->c_dev, &my_ops);
//4.添加一个字符设备
ret = cdev_add(&my_dev->c_dev, my_dev->dev_no, 1);
return 0;
}
自动创建设备节点
利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置
在驱动用加入对udev 的支持主要做的就是:在驱动初始化的代码里调用class_create(…)为该设备创建一个class,再为每个设备调用device_create(…)创建对应的设备
内核中定义的struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点
这样,加载模块的时候,用户空间中的udev会自动响应 device_create()函数,去/sysfs下寻找对应的类从而创建设备节点
这里贴出部分实现代码:
struct device *my_device;
......
//5.为该设备创建一个class
//这个类存放于sysfs下面,调用device_create函数时会在/dev目录创建相应的设备节点
cls = class_create(THIS_MODULE, "myclass");//sys/devices/virtual/myclass/my_dev
if(IS_ERR(cls))
{
unregister_chrdev_region(my_dev->dev_no,1);
return -EBUSY;
}
//6.创建对应的设备节点
//加载模块时,用户空间的udev会自动响应该函数,去/sysfs下寻找对应的类创建设备节点
my_device = device_create(cls,NULL,my_dev->dev_no,NULL,"my_dev");//mknod /dev/my_dev
if(IS_ERR(my_device))
{
class_destroy(cls);
unregister_chrdev_region(my_dev->dev_no,1);
return -EBUSY;
}
实现效果如下图:
为驱动添加open()、read()、write()、ioctl()函数
当一个字符设备被注册后,我们随即就要来操作这个字符设备,open , read , write , close等操作,需要file_operations这个结构体:
static const struct file_operations __fops = { \
.owner = THIS_MODULE, \
.open = __fops ## _open, \
.release = simple_attr_release, \
.read = simple_attr_read, \
.write = simple_attr_write, \
.llseek = generic_file_llseek, \
}
struct file_operations {
struct module *owner;
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 (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
......
那么内核是如何去识别相应的函数呢?
是通过系统调用 , 在上层应用程序,打个比方 .
通过open()打印相应的设备,那么syscall函数就会通过系统调用号识别到内核态里的函数,进而调用到我们这里实现的my_open,这就是内核态和用户态相互沟通的方式
部分代码:
//创建函数,这里就不写出具体实现了
int my_open(struct inode *inode, struct file *file){...}
int my_close(struct inode *inode, struct file *file){...}
ssize_t my_read(struct file *file,char __user *buf,size_t len,loff_t *pos){...}
ssize_t my_write(struct file *file,const char __user *buf,size_t len,loff_t *pos){...}
long my_ioctl(struct file *file,unsigned int cmd,unsigned long arg){...}
struct file_operations my_ops = {
.open = my_open,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
.release = my_close,
};
//初始化ops
cdev_init(&my_dev->c_dev, &my_ops);
最终效果图 :
Linux字符设备驱动完成!
项目源码地址:https://github.com/huchanghui123/my_cdev
参考链接:
https://blog.csdn.net/zqixiao_09/article/details/50839042
https://blog.csdn.net/morixinguan/article/details/55002774