Linxu驱动程序整体分为三个类型:字符型设备驱动、块设备驱动和网络设备驱动,其结构图如下
首先学习一下最简单的字符型驱动的写法。字符驱动是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等
如上图,在Linux内核中使用cdev结构体来描述字符设备,通过其成员dev_t来定义设备号(分为主、次设备号)以确定字符设备的唯一性。通过其成员file_operations来定义字符设备驱动提供给VFS的接口函数,如常见的open()、read()、write()等。在Linux字符设备驱动中,模块加载函数通过register_chrdev_region( ) 或alloc_chrdev_region( )来静态或者动态获取设备号,通过cdev_init( )建立cdev与file_operations之间的连接,通过cdev_add( )向系统添加一个cdev以完成注册。模块卸载函数通过cdev_del( )来注销cdev,通过unregister_chrdev_region( )来释放设备号。用户空间访问该设备的程序通过Linux系统调用,如open( )、read( )、write( ),来“调用”file_operations来定义字符设备驱动提供给VFS的接口函数。
字符型驱动程序主要做如下三件事:
1. 定义一个结构体static struct file_operations变量,在其中定义一些设备的打开、关闭、读、写、控制函数。
2. 在结构体外分别实现结构体忠定义的这些函数
3. 向内核中注册和删除驱动模块
定义结构体的格式如下
static struct file_operations myDriver_fops = {
.owner = THIS_MODULE,
.write = myDriver_write,
.read = myDriver_read,
.ioctl = myDriver_ioctl,
.open = myDriver_open,
.release = myDriver_release,
};
实现write函数操作:
static char myDriver_Buffer[1024*1024]; // 模拟字符设备数据
static sszie_t myDriver_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
sizeof_t fill_size = count;
// 超出写入缓冲长度
if(*f_pos >= sizeof(myDriver_Buffer))
{
*f_pos = sizeof(myDriver_Buffer);
return 0;
}
if((count + *f_pos )>sizeof(myDriver_Buffer))
{
*fill_size = sizeof(myDriver_Buffer) - *f_ops;
}
// 将用户数据拷贝到内核空间
copy_form_user(myDriver_Buffer[*f_ops], buf, fill_size);
*f_ops += fill_size;
return fill_size;
}
static ssize_t myDriver_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
size_t read_size = count;
if(*f_pos >= sizeof(myDriver_Buffer))
{
*f_pos = sizeof(myDriver_Buffer);
return 0;
}
if((count + *f_pos )>sizeof(myDriver_Buffer))
{
read_size = sizeof(myDriver_Buffer) - *f_ops;
}
// 将内核空间数据拷贝到用户
copy_form_user( buf, myDriver_Buffer[*f_ops], read_size);
*f_ops += read_size;
return read_size;
}
static int myDriver_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
// 模拟cmd命令值
int cmd_0 = 0;
int cmd_1 = 1;
int cmd_2 = 2;
int cmd_3 = 3;
if(_IOC_TYPE(cmd) != TSTDRV_MAGIC)
{
return -ENOTTY;
}
if(_IOC_TYPE(cmd) > TSTDRV_MAXNR)
{
return -ENOTTY;
}
switch(cmd)
{
case cmd_0:
{
// 添加处理流程
break;
}
case cmd_1:
{
// 添加处理流程
break;
}
case cmd_2:
{
// 添加处理流程
break;
}
case cmd_3:
{
// 添加处理流程
break;
}
}
return 0;
}
static int open(struct inode *inode, struct file *file)
{
// 增加驱动程序的使用次数
MOD_INC_USE_COUNT;
return 0;
}
static int myDriver_release(struct inode *inode, struct file *filp)
{
MOD_INC_USE_COUNT;
return 0;
}
static int myDriver_Major = 0;
static int __init myModule_init(void)
{
// 模块初始化
PRINTK("myMoudle_init\n");
// 驱动注册 自动分配设备号
myDriver_Major = register_chrdev(0, "myDriver", &myDriver_fops);
if(myDriver_Major < 0)
{
PRINTK("register char device fail!\n");
return myDriver_Major;
}
PRINTK("register myDriver OK! Major = %d\n", myDriver_Major);
#ifdef CONFIG_DEVFS_FS
// 创建设备目录
devfs_myDriver_dir = devfs_mk_dir(NULL, "myDriver", NULL);
// 创建设备文件
devfs_myDriver_raw = devfs_register(devfs_myDriver_dir, "raw0", DEVFS_FL_DEFAULT, myDriver_Major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &myDriver_fops, NULL);
PRINTK("add dev file to devfs OK!\n");
#endif
}
static void __exit myMoudle_exit(void)
{
// 模块退出代码
PRINTK("myModule_exit\n");
// 驱动注销
if(myDriver_Major > 0)
{
#ifdef CONFIG_DEVFS_FS
devfs_unregister(devfs_myDriver_raw);
devfs_unregister(devfs_myDriver_dir);
#endif
unregister_chrdev(myDriver_Major, "myDriver");
}
return;
}
开发字符设备驱动程序的基本步骤
1. 确定主设备号和次设备号
2. 实现驱动设备程序
(1) 实现file_operations结构体中的函数
(2) 实现初始化函数并注册字符设备
(3) 实现销毁函数并释放字符设备
3. 创建设备文件节点
❤SL--希望你能每天开心