(2.3)file_operation 实现具体操作:思考二
【思考二;内核怎样创建设备,主设备号具体应用是什么呢?】
major = register_chrdev(0, "leds_dev", &jz2440_leds_fops); //注册设备 告诉内核
> 第一个参数是主设备号,0代表动态分配。第二个参数是设备的名字,第三个参数是文件操作指针。
__register_chrdev(0, 0, 256, "leds_dev", &jz2440_leds_fops);
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
...
cd = __register_chrdev_region(major, baseminor, count, name);
/* __register_chrdev_region(0, 0, 255, "leds_dev");
调用了 __register_chrdev_region 并强制指定了起始次设备号为0,256个,把一个主设备号下的所有次设备号都申请光了。同时它还封装了 cdev_init 和 cdev_add ,倒是很省事。 */
...
cdev = cdev_alloc(); /* 分配了一个cdev结构体,并把它添加进链表 */
... // 下面几行起到了 cdev_init() 的作用
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
...
cd->cdev = cdev;
return major ? 0 : cd->major; /* 返回主设备号 */
}
简单来说就是分配一个主设备号,并把相对应的file_operation等信息存放在一个cdev结构体中。
上面涉及到的其他函数,主要是因为:Linux主设备号有限内核最多支持 256 个字符设备驱动程序。在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升。
下图很清晰的表明了其中关系,和各个函数的用处。
下面用一个示例程序表明两种注册驱动的异同:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/cdev.h>
/* 1. 确定主设备号 */
static int major;
static int hello_open(struct inode *inode, struct file *file)
{
printk("hello_open\n");
return 0;
}
static int hello2_open(struct inode *inode, struct file *file)
{
printk("hello2_open\n");
return 0;
}
/* 2. 构造file_operations */
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
};
static struct file_operations hello2_fops = {
.owner = THIS_MODULE,
.open = hello2_open,
};
#define HELLO_CNT 2
static struct cdev hello_cdev;
static struct cdev hello2_cdev;
static struct class *cls;
static int hello_init(void)
{
dev_t devid;
/* 3. 告诉内核 */
#if 0 // 老方法
/* (major, 0), (major, 1), ..., (major, 255)都对应hello_fops */
major = register_chrdev(0, "hello", &hello_fops);
#else // 新方法
if (major) {
devid = MKDEV(major, 0);
/* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops
给定主设备号 */
register_chrdev_region(devid, HELLO_CNT, "hello");
}
else
{
/* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops
自动分配主设备号 */
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
major = MAJOR(devid);
}
cdev_init(&hello_cdev, &hello_fops);
cdev_add(&hello_cdev, devid, HELLO_CNT);
/* 同一个主设备号下的不同次设备号,采用不同的fops */
devid = MKDEV(major, 2);
register_chrdev_region(devid, 1, "hello2");
cdev_init(&hello2_cdev, &hello2_fops);
cdev_add(&hello2_cdev, devid, 1);
#endif
cls = class_create(THIS_MODULE, "hello");
device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */
device_create(cls, NULL, MKDEV(major, 3), NULL, "hello3"); /* /dev/hello3 */
return 0;
}
static void hello_exit(void)
{
device_destroy(cls, MKDEV(major, 0));
device_destroy(cls, MKDEV(major, 1));
device_destroy(cls, MKDEV(major, 2));
device_destroy(cls, MKDEV(major, 3));
class_destroy(cls);
cdev_del(&hello_cdev);
unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);
cdev_del(&hello2_cdev);
unregister_chrdev_region(MKDEV(major, 2), 1);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");