linux字符设备驱动笔记

采用字节流访问的设备称位字符设备,通常字符设备只能顺序访问。也有极少数可以前后移动访问指针的设备(如:帧捕捉卡等设备)

基础知识

cdev结构体

在Linux内核中,使用cdev结构体描述一个字符设备:

struct cdev {
  struct kobject kobj;             /*  内嵌的 kobject 对象  */
  struct module *owner;            /*  所属模块 */
  struct file_operations *ops;     /*  文件操作结构体 */
  struct list_head list;
  dev_t dev;                       /*  设备号 32位 12位位主设备号,20位位次设备号 */
  unsigned int count;
};

获得主从设备号宏定义

MAJOR(dev_t dev)
MINOR(dev_t dev)

主次设备号转dev_t

MKDEV(int major, int minor)

Linux内核提供操作cdev函数

cdev_init()函数

void cdev_init(struct cdev *cdev, struct file_operations *fops)
{
	menset(cdev,o,sizeof(*cdev));
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}

用于初始化cdev成员,并于file_operations建立连接

cdev_alloc() 函数

struct cedv *cdev_alloc(void)
{
	struct cdev *p = kzalloc(sizeof(struct cdev),GFP_KERNEL);
	if(p){
		INIT_LIST_HEAD(&p->list);
		kboject_init(&p->kobj, &ktype_cdev_dynamic);
	}
	return p;
}

用于动态申请cdev内存

cdev_add()、cdev_del()

系统添加和删除一个cdev,完成字符设备的注册和注销

分配和释放设备号

int register_chrdev_region(dev_t from, unsigned count, const char *name);

用于已知起始设备号的情况

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

用于设备号未知,向系统申请设备号情况

void unregister_chrdev_region(dev_t from, unsigned count)

在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()应该被调用
以释放原先申请的设备号

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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
  ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
  int (*iterate) (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 (*aio_fsync) (struct kiocb *, 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 **);
  long (*fallocate)(struct file *file, int mode, loff_t offset,
               loff_t len);
  int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

标题字符设备驱动代码框架

/*  读设备 */
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count,
     loff_t*f_pos)
{
     ...
     copy_to_user(buf, ..., ...);
     ...
}

/*  写设备 */
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
    ...
    copy_from_user(..., buf, ...);
    ...
 }
 /* ioctl 函数  */
long xxx_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)
 {
    ...
    switch (cmd) {
    case XXX_CMD1:
         ...
         break;
    case XXX_CMD2:
         ...
         break;
    default:
         /*  不能支持的命令  */
         return  - ENOTTY;
    }
    return 0;
}

struct file_operations xxx_fops = {
	.owner = THIS_MODULE,
	.read = xxx_read,
	.write = xxx_write,
	.unlocked_ioctl = xxx_ioctl,
	...

}

/*  设备结构体 */
struct xxx_dev_t {
 struct cdev cdev;
 ...
 } xxx_dev;


/*  设备驱动模块加载函数 */
static int _ _init xxx_init(void)
{
  ...
  cdev_init(&xxx_dev.cdev, &xxx_fops);         /*  初始化 cdev */
  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)
{
   unregister_chrdev_region(xxx_dev_no, 1);      /*  释放占用的设备号 */
   cdev_del(&xxx_dev.cdev);                      /*  注销设备 */
  ...
}

由于用户空间不能直接访问内核空间的内存,因此借助了函数copy_form_user()、copy_to_user()

unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count);
unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);

上述函数均返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。如果复制失败,则返回负值。
简单类型的复制:

int val;                         /*  内核空间整型变量 */
...
get_user(val, (int *) arg);      /*  用户→内核, arg 是用户空间的地址  */
...
put_user(val, (int *) arg);      /*  内核→用户, arg 是用户空间的地址  */

实例

#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       0x01
#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;
/* file_operations 相关  */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
	loff_t ret = 0;
	switch(orig){
	case 0: /* 从头文件开头位置seek */
		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:/* 从头文件当前位置seek */
		if((filp->f_pos + offset) < 0){
			ret = -EINVAL;
			break;
		}	
		if((filp->f_pos + offset) > GLOBALMEM_SIZE){
			ret = -EINVAL;
			break;	
		}
		filp->f_pos += offset;
		ret = filp->f_pos;
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}


//ppos 是要读的位置相对于文件开头的偏移
static ssize_t globalmem_read(struct file *flip,char __user *buf,size_t size,loff_t * ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int  ret = 0;
	struct globalmem_dev *dev = flip->private_data;	
	
	if(p >= GLOBALMEM_SIZE)
	  return 0;
	if(count > GLOBALMEM_SIZE - p)
	  count = GLOBALMEM_SIZE - p;

	if(copy_to_user(buf,dev->mem+p,count))
	  return  -EFAULT;
	  else {
	    *ppos += count;
	    ret = count;
	    printk(KERN_INFO "read %u bytes(s) from %1u\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;

  if(copy_from_user(dev->mem +p,buf,count))
    ret = -EFAULT;
  else {
    *ppos +=  count;
    ret = count;

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

  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);
      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,
};




/* globalmem设备驱动模块加载函数 */
static void globalmem_setup_dev(struct globalmem_dev *dev,int index)
{
	int err,devno = MKDEV(globalmem_major,index);

	cdev_init(&dev->cdev,&globalmem_fops);
	
	dev->cdev.owner = THIS_MODULE;

	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);

	if(globalmem_major){
//		ret = register_chrdev_region(devno,1,"globalmem");
		ret = register_chrdev_region(devno,DEVICE_NUM,"globalmem");
	}else{
//		ret = alloc_chrdev_region(&devno,0,1,"globalmem");
		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);

	if(!globalmem_devp){
		ret = -ENOMEM;
		goto fail_malloc;
	}

//	globalmem_setup_dev(globalmem_devp,0);
	for(i = 0; i < DEVICE_NUM; i++)
	  globalmem_setup_dev(globalmem_devp + i,i);

	return 0;

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

static void __exit globalmem_exit(void)
{
  int i;
//  cdev_del(&globalmem_devp->cdev);
  for(i = 0; i < DEVICE_NUM; i++){
          cdev_del(&(globalmem_devp + i)->cdev);
  }
  kfree(globalmem_devp);
//  unregister_chrdev_region(MKDEV(globalmem_major,0),1);
  unregister_chrdev_region(MKDEV(globalmem_major,0),DEVICE_NUM);

}

module_init(globalmem_init);
module_exit(globalmem_exit);

MODULE_AUTHOR("lyp");
MODULE_LICENSE("GPL v2");

验证

加载编译后得到的globalmem.ko文件

sudo insmod globalmem.ko

lsmod命令查看加载模块
查看模块加载/proc/devices查看,多了主设备号为230的globalmem驱动

#cat /proc/devices

创建节点0~10

#mknod /dev/globalmem(n)  c  230  (n)

数据读写测试

$ sudo chmod 777 /dev/globalmem* 
$ echo  'hello world' > /dev/globalmem1 
$ cat /dev/globalmem1
hello world

如果启用了sysfs文件系统,将多出/sys/module/globalmem目录
refcnt记录了globalmem模块引用计数
sections下包含几个文件给出了globalmem所包含的BSS、数据段和代码段等地址信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值