问题1:上层的系统调用open是如何调用到底层的file_operation的open函数的??
解答:
首先使用ls -l /dev/* 看一下里面的设备,取tty0为例:
crw--w---- 1 root root 4, 0 2013-03-23 19:13 /dev/tty0
上面示例显示了下面几个重要信息:
c :字符设备
4:主设备号
0:次设备号
通过设备类型(c)和设备号(主设备号)来找到相应的file_operation的。在内核中有一个chrdevs数组,然后以major(主设备号)为索引,通过register_chrdev函数将file_operation挂接进去.
如下图:
图片转载来源:register_chrdev深入分析
下面总结一下open的流程:
1.在VFS中先解析这个文件的属性是什么。如果是字符设备,就转去找字符设备数组
2.在字符设备数组中,根据主设备号为索引(切记:不是根据设备的名字查找的),找到相应的file_operation
3.底层的file_opearation是由register_chrdev()注册填充的(根据major)
4.进行以上的操作,底层的file_operation和上层的系统调用就衔接上了。
问题2:在注册字符设备的时候,如何选取相应的主设备号?
方法1.手动设置:主设备号可以通过cat /proc/devices找到一个未使用的主设备号即可
方法2.自动设置:可以在register_chrdev中的主设备号设置为0,让内核自动为设备分配主设备号
问题3:/dev/ 下面的设备节点如何生成?
方法1.使用mknod命令:mknod /dev/设备名 c 主设备号 次设备号
方法2.自动创建:
使用udev机制,根据系统信息创建设备节点
什么是系统信息?进入/sys/class下面看下
自动创建的方法:
static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev; firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");
这个设备节点还是根据主设备号来自动创建的。创建的名字就是xyz。
能够自动创建设备节点的原因:
在/etc/init.d/rcS脚本中有:
echo /sbin/mdev > /proc/sys/kernel/hotplug的设置
在内核中只要insmod或rmmod都会经过这个操作。会自动的创建和销毁。
总结:
不管不手动创建还是自动创建,都是需要提供设备的主设备号的。它是找到file_operation操作的源头。
四、次设备号的用处是什么???
解答:根据个人理解,次设备号对应于驱动程序的某些操作。驱动程序可以根据次设备号来决定要对那些设备或哪些文件进行处理。假如有好几个led灯,他们的主设备号都是一样的(file_operation)。那么怎么去操作其中一个或者某些led呢?这时次设备号就有它的用处了。
下面附上代码:
首先是注册设备节点:
static struct class *leds_class; static struct class_device *leds_class_devs[4];//指针数组还是数组指针? leds_class_devs[0] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds"); /* /dev/leds */ for (minor = 1; minor < 4; minor++) /* /dev/led1,2,3 */ { leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor); if (unlikely(IS_ERR(leds_class_devs[minor]))) return PTR_ERR(leds_class_devs[minor]); }
然后就是相关的操作,取一个open为例:
static int s3c24xx_leds_open(struct inode *inode, struct file *file) { int minor = MINOR(inode->i_rdev); //MINOR(inode->i_cdev);获取次设备号 switch(minor) { case 0: /* /dev/leds */ { // 配置3引脚为输出,次设备为0的时候对所有的led进行操作 //s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP); GPFCON &= ~(0x3<<(4*2)); GPFCON |= (1<<(4*2)); //s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP); GPFCON &= ~(0x3<<(5*2)); GPFCON |= (1<<(5*2)); //s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP); GPFCON &= ~(0x3<<(6*2)); GPFCON |= (1<<(6*2)); // 都输出0 //s3c2410_gpio_setpin(S3C2410_GPF4, 0); GPFDAT &= ~(1<<4); //s3c2410_gpio_setpin(S3C2410_GPF5, 0); GPFDAT &= ~(1<<5); //s3c2410_gpio_setpin(S3C2410_GPF6, 0); GPFDAT &= ~(1<<6); down(&leds_lock); leds_status = 0x0; up(&leds_lock); break; } case 1: /* /dev/led1 次设备号为1的时候对其中一个led进行操作,下面类似*/ { s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP); s3c2410_gpio_setpin(S3C2410_GPF4, 0); down(&leds_lock); leds_status &= ~(1<<0); up(&leds_lock); break; } case 2: /* /dev/led2 */ { s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP); s3c2410_gpio_setpin(S3C2410_GPF5, 0); leds_status &= ~(1<<1); break; } case 3: /* /dev/led3 */ { s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP); s3c2410_gpio_setpin(S3C2410_GPF6, 0); down(&leds_lock); leds_status &= ~(1<<2); up(&leds_lock); break; } } return 0; }
总结:
主设备号帮我们找到了需要执行哪个驱动程序,次设备号归那个程序使用的。
单片机:操作的是物理地址
驱动:操作的是虚拟地址,虚拟地址通过ioremap函数进行映射的