2.4内核的字符设备注册接口
Linux 2.4内核中字符设备驱动的注册接口是:
int register_chrdev(unsigned int major,const char* name,struct file_operations* file)
而Linux 2.6内核已经改为如下接口,并增加了自动在/dev目录下添加设备文件等功能。
int cdev_add(struct cdev* p,dev_t dev,unsigned count)
2.4内核的注册接口依然可以在2.6内核中使用,虽然官方不建议再使用,但是个人感觉还是2.4的接口用得爽,哈哈。驱动程序结构如下。
驱动程序结构
宏__KERNEL__表示该模块将用于内核,也可以在编译时通过- D 选项指定。宏MODULE表示这个驱动程序将单独作为一个模块的方式编译和使用,而不是直接编译进内核。可以通过insmod和rmmod命令动态加载和卸载该模块,使用更加灵活。
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
声明和定义驱动程序函数,标准做法是把函数原型声明放在一个.h文件中,在文件开始处include引用,并在文件中定义。这里把声明和定义放在一起。
#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/types.h>
#include<linux/poll.h>
#define DEVICE_NAME "drv_f"
static char drv_buf[8];
static ssize_t demo_write(struct file* filp,const char* buffer,size_t count,loff_t* ppos)
{
copy_from_user(drv_buf,buffer,1);
drv_buf[0]++;
printk(KERN_ALERT "user write to driver\n");
return 0;
}
static ssize_t demo_read(struct file* filp,char* buffer,size_t count,loff_t* ppos)
{
copy_to_user(buffer,drv_buf,1);
printk("user read from driver\n");
return count;
}
//ioctl用于控制驱动程序本身的一些特性和参数,例如串行通讯的速率,驱动程序使用的缓冲区大小等等。
static long demo_ioctl(struct file* file,unsigned int cmd,unsigned long arg)
{
printk(KERN_ALERT "ioctl running\n");
switch(cmd)
{
case 1:
printk(KERN_ALERT "command 1 running\n");
break;
case 2:
printk(KERN_ALERT "command 2 running\n");
break;
default:
printk("error command number\n");
break;
}
return 0;
}
static int demo_open(struct inode* inode,struct file* file)
{
printk(KERN_ALERT "device open success\n");
return 0;
}
static int demo_release(struct inode* inode,struct file* filp)
{
printk(KERN_ALERT "device release success\n");
return 0;
}
注意,定义read和write时,由于用户空间和内核空间的映射方式完全不同,不可以用memset等函数写入内存,必须使用如下函数:
unsigned long copy_to_user(void* to,const void* from,unsigned long count);
unsigned long copy_from_user(void* to,const void* from,unsigned long count);
上述read,write,ioctl,open,release是一个字符设备驱动程序五个最基本的需要完成的函数,这五个函数对应五个不同的系统调用。定义完成后,将他们扔给file_operations结构体:
static struct file_operations demo_fops = {
.write = demo_write,
.read = demo_read,
.unlocked_ioctl = demo_ioctl,
.open = demo_open,
.release = demo_release,
};
在init函数中调用register_chrdev()函数,主设备号传入0时,函数返回动态分配的主设备号。
static int maj = -1;
static int __init demo_init(void)
{
int result;
result = register_chrdev(0,DEVICE_NAME,&demo_fops);
if(result < 0)
{
return result;
}
maj = result;
printk(DEVICE_NAME " initialized\n");
return 0;
}
static void __exit demo_exit(void)
{
unregister_chrdev(maj,DEVICE_NAME);
printk(DEVICE_NAME " unloaded\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
驱动程序的编译与加载
驱动程序编写完成后,复制到drivers/char目录下,需要修改Kconfig以及Makefile文件,并通过make menuconfig命令打开内核配置菜单,将该模块选择为动态加载模式<M>。
内核配置完成后,通过make modules命令编译该模块,在drivers/char/目录下生成一个该模块的.ko文件。
将这个文件下载到开发板,通过insmod *.ko命令就可以将模块加载到内核了。
通过cat /proc/devices命令可以查看该驱动对应的主设备号,我这里是253
此时可以看到/dev目录下没有该设备的设备文件结点,需要手动创建一个,命令为 mknod /dev/my_device c {你的主设备号} {次设备号},这里c代表字符设备。这时候可以通过ls /dev -l查看到这个设备文件节点了。