对于Linux而言,一切皆文件,在Linux系统下,所有文件都可以像文本文件一样open、read、write,那么对于Linux设备驱动而言,比如现在有一个点灯的驱动程序,它的设备节点是/dev/xxx,当应用程序执行open、read、write的时候,是如何调用到驱动程序里的open、read、write的呢,
user space:
APP:(应用程序) open read write
kernel space:
system call
VFS sys_open sys_read sys_write
device driver xxx_open xxx_read xxx_write
总体流程:APP调用open时最终会调用到VFS里的sys_open,sys_open根据文件的属性以及主设备号,去对应的数据结构里边找到驱动程序在加载到内核时添加到相应数据结构中的file_operation结构体,file_operation的内容如下
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.write = xxx_write,
.read = xxx_read,
};
总的来说就是驱动程序会将这些信息告诉给内核,也就是内核在某个数据结构中根据主设备号将这些信息记录起来,当应用程序执行对应的函数时,会根据文件提供的属性以及主设备号在这个数据结构中查找对应的项,最后找到该设备的file_operation,然后通过这个结构题调用驱动的open、read、write等。
驱动程序中如何去注册这些信息呢,使用下面的这个函数去实现注册,如下:
register_chrdev(major, "driver_name", &xxx_fops);
第一项为 主设备号(为0时,系统自动分配,然后返回主设备号),第二项是名字(不重要),第三项是关联的file_operation,这里就能猜到,其实驱动的file_operation是和主设备号绑定的。到这儿就有问题了,应用程序里的/dev/xxx是如何来的呢?
可以使用 mknod /dev/xxx c major minor手动创建,也可以在驱动程序里自动创建,显然,让驱动程序自动创建更为合理,那么如何创建呢,这里也涉及到mdev,这里就不详细的去说明了(mdev),注册一个设备驱动时,会在/sys/目录下生成一些信息,mdev的作用就是根据这些信息去生成设备节点,也就是/dev/xxx,那么我们驱动程序里只负责提供这些信息即可,一个驱动如何提供这些信息呢?如下
...........
static struct class *xxx_class;
static struct class_device *xxx_class_dev;
...........
static int first_drv_init(void)
{
................
major = register_chrdev(0, "xxx", &xxx_fops); // 注册, 告诉内核
xxx_class = class_create(THIS_MODULE, "xxx");
xxx_class_dev = class_device_create(xxx_class, NULL, MKDEV(major, 0), NULL, "xxx"); /* /dev/xxx */
................
return 0;
}
...........
然后就可以在/sys/class/这个目录下找到对应的文件夹,文件夹下就是对应的设备信息,关于驱动程序加载和卸载的时候mdev为什么能够自动的创建和删除相应的信息(前面说到mdev会自动创建这些信息,那么就应该可以自动删除这些信息),这儿就涉及到hotplug机制,以后进一步了解
附上程序:
APP:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/xxx", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
if (argc != 2)
{
printf("Usage :\n");
printf("%s <on|off>\n", argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
device driver:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *xxx_class;
static struct class_device *xxx_class_dev;
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
static int xxx_open(struct inode *inode, struct file *file)
{
//printk("xxx_open\n");
/* 配置GPF4,5,6为输出 */
*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
return 0;
}
static ssize_t xxx_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
//printk("xxx_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 点灯
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
}
else
{
// 灭灯
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
}
return 0;
}
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.write = xxx_write,
};
int major;
static int xxx_init(void)
{
major = register_chrdev(0, "xxx", &xxx_fops); // 注册, 告诉内核
xxx_class = class_create(THIS_MODULE, "xxx");
xxx_class_dev = class_device_create(xxx_class, NULL, MKDEV(major, 0), NULL, "xxx"); /* /dev/xxx */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
static void xxx_exit(void)
{
unregister_chrdev(major, "xxx"); // 卸载
class_device_unregister(xxx_class_dev);
class_destroy(xxx_class);
iounmap(gpfcon);
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
以上是个人学习后对此比较简单的总结,不足之处请指教