设备号
设备简介
ls /dev/* 的设备
这些就是本机的设备
驱动是有设备号的?主设备和从设备
cat /proc/devices 查看设备号
其中其中第一列是设备号,第二列是设备名
lsblk 命令可以查看块设备的主设备号和次设备号
MJA列是主设备号,MIN是次设备号
驱动是如何在这个文件里面创建设备并分配设备号的那?
cdev结构体
<include/linux/cdev.h>
struct cdev {
struct kobject kobj; //内嵌的内核对象.
struct module *owner; //该字符设备所在的内核模块的对象指针.
const struct file_operations *ops; //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.
struct list_head list; //用来将已经向内核注册的所有字符设备形成链表.
dev_t dev; //字符设备的设备号,由主设备号和次设备号构成.
unsigned int count; //隶属于同一主设备号的次设备号的个数.
};
dev_t dev 定义了设备号,为32位,12位为主设备号,20位为从设备号
备注:因为主设备号总共占12bit,所以范围为0 ~ 2^12,即:0~4095。所以理论上主设备号是会被用完的,但是真实情况基本不会发生。
内核提供了一组cdev相关的函数来操作它。
void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
void cdev_del(struct cdev *p);
cdev_init 是初始化cdev相关
cdev_alloc 为cdev 分配内存
cdev_add 向系统添加cdev
cdev_del 向系统删除cdev
Linux 字符设备驱动结构—— cdev 结构体、设备号相关知识解析及创建字符设备示例_linux内核字符设备驱动涉及的结构体-CSDN博客
在用cdev_add注册之前,应该向系统申请设备号
申请设备号
1.静态申请
int register_chrdev_region(dev_t from, unsigned count, const char *name)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
2.动态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
可以看到二者都是调用了__register_chrdev_region 其中静态申请的设备号是自己指定的,动态的则是系统分配,所以静态申请代码会在指定的设备号存在的时候会执行失败,而动态申请则是系统将空闲的设备号分配给你。
设备节点
如果想产生调试接口则调用 sys 或者proc该怎么操作呢?
linux字符驱动的模块
driver_module.c
//设备结构体
struct xxx_dev_t
{
struct cdev cdev;
...
}
//设备驱动模块加载函数
static init __init xxx_init(void)
{
...
cdev_init(&xxx_dev.cdev,&xxx_fops);
xxx_dev.cdev.owner = THIS_MODULE;
if(xxx_major)
{
register_chrdev_region(xxx_dev_no,1,DEV_NAME);
}
else
{
alloc_chrdev_region(&xxx_dev_no,0,1,DEV_NAME);
}
ret = cdev_add(&xxx_dev,cdev,xxx_dev_no,1);
...
}
static void __exit xxx_exit(void)
{
unrgister_chrdev_region(xxx_dev_no,1);
cdev_del(&xxx_dev.cdev);
}
file_operations 结构体
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 *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} ;
file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。读取file_operation
中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。
注:file_operation就是我们的转换器,将内核函数转换成用户函数,这个是个人理解,实际还是像上面所说的那样是通过file_operation结构体中的函数指针赋值给我们的驱动函数。
下面是fileoperation的简单实例
sturct file_operations xxx_fops{
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
.unlocked_iocrl = xxx_ioctl,
};
其中的.owner等就是file_operation中的函数,而xxx_read则是下面要说的驱动函数
字符设备驱动读写、io控制
还是简单的示例
ssize_t xxx_read (struct file *filep, char __user *buf, size_t count, loff_t *f_pos)
{
...
copy_to_user(buf,...,...);
...
}
ssize_t xxx_write (struct file *filep, const char __user *buf, size_t count, loff_t *f_pos)
{
...
copy_from_user(...,buf,...);
...
}
long xxx_ioctl (struct file *filep, unsigned int cmd, unsigned long arg)
{
...
switch(cmd)
{
case XXX_CMD1:
...
break;
case XXX_CMD2:
...
break;
case XXX_CMD3:
...
break;
default:
return -ENOTTY;
}
return 0;
}
这三个就是常用的驱动函数分别是读写和io控制。
其中的
copy_to_user() 完成内核空间到用户空间的复制
copy_from_user()完成用户空间到内核空间的复制
-
copy_to_user
To 目标地址,这个地址是用户空间的地址;
From 源地址,这个地址是内核空间的地址;
N 将要拷贝的数据的字节数。 -
copy_from_user
To 目标地址,这个地址是内核空间的地址;
From 源地址,这个地址是用户空间的地址;
N 将要拷贝的数据的字节数。
最后就是在Linux下创建字符设备节点的命令
sudo mknod /dev/globalmem c 230 0
这个指令的参数分别是sudo是管理员权限,mknod是创建节点的命令,/dev/globalmem是设备节点的路径,c表示字符设备,230是主设备号,0是次设备号。
创建设备节点后,用户可以通过该设备节点与设备进行通信,实现对设备的控制或者数据传输等操作。