第二节 简单的字符驱动编写

设备号

设备简介

ls /dev/* 的设备

7e6e327954714111831f211b1d5ceaed.png

这些就是本机的设备

驱动是有设备号的?主设备和从设备
cat /proc/devices 查看设备号

20e7028ef7f244b7b1d9fd80e0f8c3e3.png

其中其中第一列是设备号,第二列是设备名

lsblk 命令可以查看块设备的主设备号和次设备号

32e7f7f46793404d9e81db5431737284.png

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位为从设备号e9cc0997a3e943fa8756b69e8f502900.png

备注:因为主设备号总共占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是次设备号。

创建设备节点后,用户可以通过该设备节点与设备进行通信,实现对设备的控制或者数据传输等操作。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值