字符设备的注册与操作

原创 2013年12月04日 10:40:21

       相对于块设备来说,字符设备的使用要简单很多。但是简单的东西,也有很多值得一看的东西。比方说,字符设备,与inode如何关联;在打开字符设备的时候,又是如何层层递进,最终执行相应的从设备的实际例程呢?下面拿Mem.c这个文件下面的例子来分析,该字符设备的主设备号为1,文件为/dev/mem,含义是物理内存。

注册:

1、chr_dev_init:

/* 内存字符设备初始化*/
static int __init chr_dev_init(void)
{
	int i;
	int err;

	err = bdi_init(&zero_bdi);
	if (err)
		return err;

	/* key:负责内存操作的字符设备的初始化,将分配器memory_fops赋值给cdev->ops*/
	if (register_chrdev(MEM_MAJOR,"mem",&memory_fops))  
		printk("unable to get major %d for memory devs\n", MEM_MAJOR);
	/* 创建class对象 ,为sysfs系统使用 */
	mem_class = class_create(THIS_MODULE, "mem");
	for (i = 0; i < ARRAY_SIZE(devlist); i++)
		device_create(mem_class, NULL,
			      MKDEV(MEM_MAJOR, devlist[i].minor),
			      devlist[i].name);

	return 0;
}
2、register_chrdev:

/**
 * register_chrdev() - Register a major number for character devices.
 * register_chrdev() - 为字符设备注册一个主设备号
 * @major: major device number or 0 for dynamic allocation
 * @name: name of this range of devices
 * @fops: file operations associated with this devices
 *
 * If @major == 0 this functions will dynamically allocate a major and return
 * its number.
 *
 * If @major > 0 this function will attempt to reserve a device with the given
 * major number and will return zero on success.
 *
 * Returns a -ve errno on failure.
 *
 * The name of this device has nothing to do with the name of the device in
 * /dev. It only helps to keep track of the different owners of devices. If
 * your module name has only one type of devices it's ok to use e.g. the name
 * of the module here.
 *
 * This function registers a range of 256 minor numbers. The first minor number
 * is 0.
 * 该函数注册一个从设备范围,有256个从设备号。第一个
 * 从设备号是0.
 */
int register_chrdev(unsigned int major, const char *name,
		    const struct file_operations *fops)
{
	struct char_device_struct *cd;
	struct cdev *cdev;
	char *s;
	int err = -ENOMEM;

	cd = __register_chrdev_region(major, 0, 256, name);
	if (IS_ERR(cd))
		return PTR_ERR(cd);
	
	cdev = cdev_alloc();  /* 分配内存:一个cdev结构实例*/
	if (!cdev)
		goto out2;

	cdev->owner = fops->owner; 
	cdev->ops = fops; /* 这里的实际效果是,刚刚申请的cdev实例代表mem设备,其中的cdev->ops指针指向相应的memory_fops,在实际操作时,先根据主设备号选择cdev,在根据从设备号从memory_fops里选择相应的实际例程*/
	kobject_set_name(&cdev->kobj, "%s", name);
	for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
		*s = '!';
		
	err = cdev_add(cdev, MKDEV(cd->major, 0), 256); /* 将初始化完毕的cdev内存设备添加到字符设备数据库,即散列表中*/
	if (err)
		goto out;

	cd->cdev = cdev;

	return major ? 0 : cd->major;
out:
	kobject_put(&cdev->kobj);
out2:
	kfree(__unregister_chrdev_region(cd->major, 0, 256));
	return err;
}

打开设备文件

1、在linux下,一切皆为文件,所以,我们这里讨论的mem字符设备也要与系统的文件系统结合。关键的概念在于inode:在打开一个设备文件时,各种文件系统的实现会调用init_special_inode函数,为mem设备创建它在文件系统中的表示inode:

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
	inode->i_mode = mode;
	if (S_ISCHR(mode)) { /* 在这里显然是进入这个分支,根据mode选择文件类型。*/
		inode->i_fop = &def_chr_fops;/* 给定inode相关的文件操作指针,至此,inode->i_fop->open = chrdev_open */
		inode->i_rdev = rdev;	/* 给定inode设备的主设备号*/
	} else if (S_ISBLK(mode)) {
		inode->i_fop = &def_blk_fops;
		inode->i_rdev = rdev;
	} else if (S_ISFIFO(mode))
		inode->i_fop = &def_fifo_fops;
	else if (S_ISSOCK(mode))
		inode->i_fop = &bad_sock_fops;
	else
		printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n",
		       mode);
}
2、chrdev_open:

/*
 * Called every time a character special file is opened
 * 每次当一个字符设备文件被打开时,会调用该函数
 */
int chrdev_open(struct inode * inode, struct file * filp)
{
	struct cdev *p;
	struct cdev *new = NULL;
	int ret = 0;

	spin_lock(&cdev_lock);
	p = inode->i_cdev;  /* 获取与mem相关的cdev,即在注册的时候分配的那个结构体实例 */
	if (!p) {
		/* 如果设备文件的inode此前没有被打开过*/
		struct kobject *kobj;
		int idx;
		spin_unlock(&cdev_lock);
		/* 根据主设备号查询字符设备数据库,即从散列表中查*/
		kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
		if (!kobj)
			return -ENXIO;
		new = container_of(kobj, struct cdev, kobj); /* 获取struct cdev 实例*/
		spin_lock(&cdev_lock);
		p = inode->i_cdev;
		if (!p) {
			inode->i_cdev = p = new;  /* 初始化inode实例*/
			inode->i_cindex = idx;
			list_add(&inode->i_devices, &p->list);/* 将该inode添加到cdev->list中,即inode中的i_devices用作链表元素*/
			new = NULL;
		} else if (!cdev_get(p))
			ret = -ENXIO;
	} else if (!cdev_get(p))
		ret = -ENXIO;
	spin_unlock(&cdev_lock);
	cdev_put(new);
	if (ret)
		return ret;
	 /* 找到指定于设备的file_operations ,这里就通过inode找到相应的cdev->ops,即memory_fops*/
	filp->f_op = fops_get(p->ops);
	if (!filp->f_op) {
		cdev_put(p);
		return -ENXIO;
	}
	/* 执行打开操作*/
	if (filp->f_op->open) {
		lock_kernel();
		ret = filp->f_op->open(inode,filp);/* 执行打开操作,这里filp->f_op->open执行的其实是memory_open函数*/
		unlock_kernel();
	}
	if (ret)
		cdev_put(p);
	return ret;
}
3、memory_open函数:作为一个分配器,根据从设备号,执行更加具体的不同的字符操作

static int memory_open(struct inode * inode, struct file * filp)
{
	switch (iminor(inode)) { /*根据从设备号区分各个设备,并且选择适当的文件操作:mem_fops、kmem_fops等等,这里选择mem_fops*/
		case 1:
			filp->f_op = &mem_fops;
			filp->f_mapping->backing_dev_info =
				&directly_mappable_cdev_bdi;
			break;
		case 2:
			filp->f_op = &kmem_fops;
			filp->f_mapping->backing_dev_info =
				&directly_mappable_cdev_bdi;
			break;
		case 3:
			filp->f_op = &null_fops;
			break;
#ifdef CONFIG_DEVPORT
		case 4:
			filp->f_op = &port_fops;
			break;
#endif
		case 5:
			filp->f_mapping->backing_dev_info = &zero_bdi;
			filp->f_op = &zero_fops;
			break;
		case 7:
			filp->f_op = &full_fops;
			break;
		case 8:
			filp->f_op = &random_fops;
			break;
		case 9:
			filp->f_op = &urandom_fops;
			break;
		case 11:
			filp->f_op = &kmsg_fops;
			break;
#ifdef CONFIG_CRASH_DUMP
		case 12:
			filp->f_op = &oldmem_fops;
			break;
#endif
		default:
			return -ENXIO;
	}
	if (filp->f_op && filp->f_op->open)
		return filp->f_op->open(inode,filp);
	return 0;
}


读操作等:

在打开设备文件之后,实际上文件指针file指向的是mem_ops(代码体现:filp->f_op = &mem_fops;),所以再执行其他的操作如下,就顺理成章了,成为简单的函数调用了:
static const struct file_operations mem_fops = {
	.llseek		= memory_lseek,
	.read		= read_mem,
	.write		= write_mem,
	.mmap		= mmap_mem,
	.open		= open_mem,
	.get_unmapped_area = get_unmapped_area_mem,
};



总结:最初只知道打开字符设备的一般函数,然后由打开与内存相关的设备文件的具体函数所替代。接下来根据选择的从设备号,进一步细化函数指针。




版权声明:本文为博主原创文章,未经博主允许不得转载。

Linux字符设备驱动和杂项设备驱动对比

初学Linux驱动程序的时候,可能对什么是字符设备驱动(char device)和杂项设备驱动(misc device)并不是很了解,更谈不上如何区分了。我自己当初在学习Linux字符设备驱动的时候,...
  • JansonZhe
  • JansonZhe
  • 2016年01月22日 16:43
  • 2112

操作系统——I/O设备管理(2)

I/O软件 为了更好地管理系统中的输入输出设备,我们需要哪一些软件?这些软件各自完成什么样的功能?它们之间的相互关系、组织结构是什么?在这些软件中,程序员负责做什么?操作系统负责做什么?I/O设备厂商...
  • yaoxiaokui
  • yaoxiaokui
  • 2015年01月29日 09:15
  • 1383

字符型设备驱动程序--gpio 驱动实例

概述: 字符设备驱动程序: 是按照字符设备要求完成的由操作系统调用的代码。 重点理解以下内容:  1. 驱动是写给操作系统的代码,它不是直接给用户层程序调用的,而是给系统调用的  2. 所以驱动要向系...
  • hejinjing_tom_com
  • hejinjing_tom_com
  • 2014年05月09日 18:03
  • 4815

字符设备的注册

  • 2013年09月04日 16:39
  • 18KB
  • 下载

新手总结linux驱动开发(字符设备驱动注册)

linux设备字符设备驱动的注册分析
  • w906787
  • w906787
  • 2017年03月30日 08:47
  • 124

Linux驱动编程--字符设备文件注册

CreateClass DestroyClass
  • u012301943
  • u012301943
  • 2014年02月07日 20:12
  • 621

Linux字符设备注册函数 register_chrdev详解

Linux字符设备注册函数 register_chrdev详解   当我们需要注册字符设备的时候,需要module_init()中调用register_chrdev() 注册。 下面主要介绍接口的实现...
  • wuheshi
  • wuheshi
  • 2016年10月24日 18:03
  • 4831

linux字符设备注册相关函数

本文介绍linux字符设备注册相关的四个函数:cdev_alloc、cdev_init、cdev_add和cdev_del。这四个函数在文件:fs/char_dev.c中定义,在头文件include/...
  • caijp1090
  • caijp1090
  • 2012年04月16日 20:56
  • 442

Linux字符设备驱动程序的注册

字符设各是Linux中最简单的设备。  在Linux中,字符设各是用一个叫做字符设备结构的数据结构char device struct来描述的。为了管理上的方便,系统维护了一个数组chrdevsLJ,...
  • hainix
  • hainix
  • 2011年03月26日 23:39
  • 488

Linux字符设备驱动的注册

很多学习Linux编程的新人都会被字符设备注册搞糊涂了,我刚开始也一样糊里糊涂的,看到网上例程有各种版本,就是调用module_init时传递的实参,先记为xxx_init()。大家可能还会看到杂项设...
  • zhoutuan1
  • zhoutuan1
  • 2016年07月20日 10:06
  • 219
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:字符设备的注册与操作
举报原因:
原因补充:

(最多只允许输入30个字)