Linux驱动学习:字符设备驱动


基于宋宝华老师的《Linux设备驱动开发详解》

Linux字符设备驱动结构

cdev结构体

cdev.h头文件下

struct cdev {
	struct kobject kobj; //内嵌的kobject对象
	struct module *owner;	//所属模块
	const struct file_operations *ops;	//文件操作结构体
	struct list_head list;
	dev_t dev; //设备号,高12位为主设备号,低20位为次设备号
	unsigned int count;
};
  1. 获得主设备号:MAJOR(dev_t dev)
  2. 获得次设备号:MINOR(dev_t dev)
  3. 通过主设备号和次设备号生成dev_t设备号:MKDEV(int major,int minor)
  4. cdev_init():初始化cdev。从栈中获取内存
  5. cdev_alloc():动态申请一个cdev内存。
  6. cdev_add():向系统添加一个cdev,即注册。通常用于__init函数
  7. cdev_del():删除一个cdev,即注销。通常用于__exit函数

分配和释放设备号

  1. register_chrdev_region():静态向系统中注册设备号,用于已知起始设备的设备号的情况
  2. alloc_chrdev_region():动态向系统申请未被占用的设备号。
  3. unregister_chrdev_region():释放申请的设备号

file_opreation结构体

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 *);
	unsigned int (*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 *);
	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);
	ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
			u64);
};

部分解释如下

  1. llseek():修改一个文件的当前读写位置,类似用户空间中的lseek()
  2. read():对应用户空间中的read()
  3. write():对应用户空间中的write(),如果该函数没有实现,用户进行write()时,返回-EINVAL
  4. unlocked_ioctl():提供设备相关控制指令的实现,调用成功,返回非负值。对应用户空间中的fcntl()ioctl()
  5. mmap():将设备内存映射到进程的虚拟地址空间中,如果驱动未实现该函数,但用户空间进行了mmap(),则返回-ENODEV。主要用于帧缓冲等设备。对应用户空间的mmap()
  6. open():对应open()。可以不实现,这种情况下,设备的打开操作永远成功
  7. release():用户空间中没有,主要是被自动的回调,用于释放设备资源和清理
  8. poll():用于询问设备是否可被非阻塞地立即读写
  9. aio_read():异步读
  10. aio_write():异步写

Linux字符设备驱动的组成

  1. copy_to_user():完成内核空间到用户空间缓冲区的复制
  2. copy_from_user:完成用户空间缓冲区到内核空间的复制、
    1. 均返回不能被复制的字节数。
    2. 完全复制成功,返回0。复制失败,返回负值。
  3. 简单类型的传递
    1. get_user:用户空间到内核
    2. put_user:内核到用户空间
  4. access_ok():检查传递的用户空间的缓冲区的合法性
    1. VERIFY_WRITE:检查写
    2. VERIFY_READ:检查读
  5. 不检查合法性,进行内存传递
    1. __get_user()
    2. __put_user()

支持N个简单字符设备的驱动

ioctl()命令

  1. Linux内核推荐采用一套统一的ioctl()命令生成方式,格式如下
    1. 8位设备类型+8位序列号+2位方向+13/14位数据尺寸
    2. 设备类型字段:为一个“幻数”,范围:0~0xff,内核中的ioctl-number.txt给出了一些推荐的和已经被使用的“幻数”,新设备驱动定义时要避免重读。
    3. 2位方向:表示从应用程序的角度来看的数据传送的方向
      1. _IOC_NONE:无数据传输
      2. _IOC_READ:读
      3. _IOC_WRITE:写
      4. _IOC_READ|_IOC_WRITE:双向
  2. 辅助生成命令的宏
    1. _IO()
    2. _IOR(type,nr,size)
    3. _IOW(设备类型,序列号,数据尺寸)
    4. _IOWR()
  3. 预定义命令
    1. 内核中已经预定义好了一些I/O控制命令,这些命令是会被内核处理,所以我没在自定义设备驱动的命令时要注意避免冲突。
      1. 如果冲突了,那么当内核收到命令时,这些命令会被内核处理,而不是交给设备驱动处理。
    2. 常用的预定义命令
      1. FIOCLEX:对文件设置专用标志,通知内核当exec()系统调用发生时自动关闭打开的文件
      2. FIONCLEX:清除FIOCLEX命令设置的标志
      3. FIOQSIE:获得一个文件或者目录的大小,用于设备文件时会返回ENOTTY错误
      4. FIONBIO:调用修改在file->f_flags中的O_NONBLOCK标志
  4. private_data指针一般指向自定义的设备结构体
  5. container_of(目标结构体成员指针,整个结构体的类型,目标结构体成员的类型):通过结构体成员的指针找到对应结构体的指针
  6. 代码中通过申请:sizeof(struct globalmem_dev)*DEVICE_NUM这么大一个空间来存储所有的结构体,所以这里其实可以视为一个数组

手动加载驱动

  1. sudo insmod globalmem.ko:加载驱动
  2. lsmod:可以查看到加载后的驱动
  3. cat /proc/devices:可以查看到主设备号为230的globalmem字符设备驱动
  4. sudo mknod /dev/globalmem c 230 0:创建设备节点
    1. 如向创建多个设备节点,那就多次运行mknod
      1. 如:sudo mknod /dev/globalmem1 c 230 1
    2. 可能还需要chmod 666 /dev/globalmem,修改设备权限
    3. 测试就是用echo "xxx" >> /dev/globalmem命令和cat /dev/globalmem命令
  5. 启用了sysfs文件系统的情况下,会多出/sys/module/globalmem目录

示例代码

源代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define GLOBALMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 230

#define DEVICE_NUM 10

static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major,int,S_IRUGO);

struct globalmem_dev{
	struct cdev cdev;
	unsigned char mem[GLOBALMEM_SIZE];
};

struct globalmem_dev* globalmem_devp;

static ssize_t globalmem_read (struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long p = *ppos;//获得相对于文件开头的偏移
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev* dev = filp->private_data;//将存放在私有数据空间中的自定义结构体取出

	//整个文件大小,也就是缓冲区大小为自定义的GLOBALMEM_SIZE
	if(p >= GLOBALMEM_SIZE){
		return 0;
	}
	if(count > GLOBALMEM_SIZE - p){
		count = GLOBALMEM_SIZE - p;
	}
	//复制数据到用户空间
	if(copy_to_user(buf,dev->mem+p,count)){
		ret = -EFAULT;
	}else {
		*ppos += count;
		ret = count;

		printk(KERN_INFO "read %u bytes(s) from %lu\n",count,p);
	}

	return ret;
}


static ssize_t globalmem_write (struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;

	if(p >= GLOBALMEM_SIZE){
		return 0;
	}
	if(count > GLOBALMEM_SIZE - p){
		count = GLOBALMEM_SIZE - p;
	}

	//将数据copy到内核空间
	if(copy_from_user(dev->mem+p,buf,count)){
		ret = -EFAULT;
	}else {
		*ppos += count;
		ret = count;

		printk(KERN_INFO "written %u bytes(s) from %lu\n", count,p);
	}
	return ret;
}

//orig这一位代表lseek的最后一位参数,offset表示传进来的偏移量
static loff_t globalmem_llseek (struct file *filp, loff_t offset, int orig)
{
	loff_t ret = 0;
	switch(orig){
		case 0:{//从文件头开始
			       if(offset < 0){
				       ret = -EINVAL;
				       break;
			       }
			       if((unsigned int)offset > GLOBALMEM_SIZE){
				       ret = -EINVAL;
				       break;
			       }

			       //file结构体中的f_pos代表的是文件的当前读写位置
			       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;
		       }

		case 2:{//从文件的尾巴
			       if(offset > 0){
				       ret = -EINVAL;
				       break;
			       }
			       if((unsigned int)offset < -GLOBALMEM_SIZE){
				       ret = -EINVAL;
				       break;
			       }

			       filp->f_pos = (unsigned int)offset + GLOBALMEM_SIZE;

			       ret = filp->f_pos;
			       break;
		       }

		default:
		       ret = -EINVAL;
		       break;
	}
	return ret;

}

static long globalmem_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct globalmem_dev* dev = filp->private_data;

	switch(cmd){
		case MEM_CLEAR:{
				       //清空
				       memset(dev->mem,0,GLOBALMEM_SIZE);
				       //将偏移量转回0
				       filp->f_pos = 0;
				       printk(KERN_INFO "globalmem  is set to zero\n");
				       break;
			       }

		default:
			       return -EINVAL;
	}

	return 0;
}

static int globalmem_open (struct inode *inode, struct file *filp)
{

	struct globalmem_dev* dev = container_of(inode->i_cdev,struct globalmem_dev,cdev);


	filp->private_data =dev;
	return 0;
}

static int globalmem_release(struct inode *inode, struct file *filp)
{
	return 0;
}


static const struct file_operations globalmem_fops={
	.owner = THIS_MODULE,
	.llseek = globalmem_llseek,
	.read = globalmem_read,
	.write = globalmem_write,
	.unlocked_ioctl = globalmem_ioctl,
	.open = globalmem_open,
	.release = globalmem_release,
};






static void globalmem_setup_cdev(struct globalmem_dev *dev,int index)
{
	//生成设备号
	int err, devno=MKDEV(globalmem_major,index);
	//初始化cdev
	cdev_init(&dev->cdev,&globalmem_fops);
	dev->cdev.owner = THIS_MODULE;
	//向系统添加一个cdev
	err = cdev_add(&dev->cdev,devno,1);

	if(err){
		printk(KERN_NOTICE "Error %d adding globalmem%d",err,index);
	}

}


static int __init globalmem_init(void)
{
	int ret;
	int i;
	dev_t devno = MKDEV(globalmem_major,0);//生成设备号
	//判断globalmem_major是否为0,为0代表动态分配
	if(globalmem_major){
		ret = register_chrdev_region(devno,DEVICE_NUM,"globalmem");
	}else {
		ret = alloc_chrdev_region(&devno,0,DEVICE_NUM,"globalmem");
		globalmem_major = MAJOR(devno);
	}

	if(ret < 0){
		return ret;
	}

	globalmem_devp = kzalloc(sizeof(struct globalmem_dev)*DEVICE_NUM,GFP_KERNEL);//GFP_KERNEL,阻塞等待内存分配完成
	if(!globalmem_devp){
		ret = -ENOMEM;
		goto fail_malloc;
	}

	for(i=0; i<DEVICE_NUM;++i){
		globalmem_setup_cdev(globalmem_devp,i);
	}

	return 0;

fail_malloc:
	unregister_chrdev_region(devno,DEVICE_NUM);
	return ret;
}

module_init(globalmem_init);

static void __exit globalmem_exit(void)
{
	int i;
	for(i=0;i<DEVICE_NUM;++i){
		cdev_del(&(globalmem_devp+i)->cdev);
	}

	kfree(globalmem_devp);
	unregister_chrdev_region(MKDEV(globalmem_major,0),DEVICE_NUM);

}

module_exit(globalmem_exit);

MODULE_AUTHOR("Barry song <111>");
MODULE_LICENSE("GPL v2");

makefile

# Kernel modules
obj-m += globalmem.o

# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0

build: kernel_modules

kernel_modules:
        make -C /交叉编译后的Linux内核源码目录 M=$(CURDIR) modules
clean:
        make -C /交叉编译后的Linux内核源码目录 M=$(CURDIR) clean
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值