我所理解的Linux设备驱动程序工作原理:
在Linux中,一切设备都看做文件来处理,在用户程序需要对硬件设备进行数据读写操作时,可以像读写一般磁盘文件的操作方式一样,通过open,write,read等系统调用来进行数据读写操作。在用户程序使用这些系统调用时,操作系统通过一个名为struct file_operations的数据结构来找到驱动程序中与系统调用对应函数,然后把系统调用应实现的功能交给该函数。所以在驱动程序的设计中,主要的工作就是实现struct file_operations结构中的函数功能(这样就把系统调用与驱动程序关联起来了),然后通过内核模块动态加载驱动程序到系统内核中,这样就一个简单的驱动程序就实现了。
主设备号:用来标识设备对应的驱动程序
次设备号:次设备号由内核使用,用于正确确定设备文件所指的设备(指向哪一个硬件设备)
在内核中,dev_t类型用来保存设备编号(包括主设备编号与次设备编号),MAJOR()与MINOR()宏可以获得主设备号与次设备号。
编写Linux设备驱动程序的一般流程:
1. 从操作系统获取设备号(主设备号与次设备号)
2. 向操作系统注册设备驱动。
3. 把系统调用open,write,read等统通过struct file_operations结构关联到设备驱动程序,让设备驱动程序来完成特定的功能。
4. 向操作系统注销设备驱动。
5. 释放从系统获得的设备号。
下面是自己写的一个完整的字符设备驱动程序,通过模块加载即可
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/fcntl.h>
#include <linux/kdev_t.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
MODULE_LICENSE("GPL");
const int MAXSIZE = 4096;
//自定义自己的字符设备
struct Char_dev{
char buff[4096];
unsigned long size;
struct cdev c_dev;
};
int myOpen(struct inode *, struct file *);
ssize_t myRead(struct file *, __user char *, size_t, loff_t *);
ssize_t myWrite(struct file *, const __user char *, size_t, loff_t *);
loff_t myLlseek(struct file*, loff_t, int);
int myRelease(struct inode *, struct file *);
//初始化 file_operations结构体,用来关联系统调用函数
struct file_operations myFops = {
.owner = THIS_MODULE,
.open = myOpen,
.release = myRelease,
.read = myRead,
.write = myWrite,
.llseek = myLlseek,
};
dev_t dev_number; //设备号
struct Char_dev myDev;
//模块的初始化与清理部分************************************************
static int __init myDevInit(void)
{
//1. 分配设备编号
int ret;
ret = alloc_chrdev_region(&dev_number, 0, 1, "myCharDev");
if (ret < 0)
{
printk(KERN_ALERT "分配设备号失败!\n");
return ret;
}
//2. 分配并注册字符设备
//初始化cdev 结构
cdev_init(&(myDev.c_dev), &myFops);
myDev.c_dev.ops = &myFops;
myDev.c_dev.owner = THIS_MODULE;
//注册字符设备
ret = cdev_add(&(myDev.c_dev), dev_number, 1);
if (ret)
{
printk(KERN_ALERT "注册设备失败!\n");
return ret;
}
return 0;
}
static void __exit myDevExit(void)
{
//1. 移除设备
cdev_del(&(myDev.c_dev));
//2. 释放设备号
unregister_chrdev_region(dev_number, 1);
}
module_init(myDevInit);
module_exit(myDevExit);
//实现 open 函数****************************************************
int myOpen(struct inode *p_inode, struct file *p_file)
{
struct Char_dev *p_myDev;
p_myDev = container_of(p_inode‐>i_cdev, struct Char_dev, c_dev);
p_file‐>private_data = p_myDev;
p_file‐>f_pos = 0;
return 0;
}
//实现 release 函数‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
int myRelease(struct inode *P_inode, struct file *p_file)
{
return 0;
}
//实现 read 函数*****************************************************
ssize_t myRead(struct file *p_file, char __user *buff, size_t count, loff_t *f_pos)
{
int err;
struct Char_dev *p_myDev;
p_myDev = p_file‐>private_data;
printk(KERN_ALERT "读取数据前设备文件大小:%lu Byte\n", p_myDev‐>size);
printk(KERN_ALERT "读取数据前文件指针位置:%lld\n", *f_pos);
if (p_myDev‐>size == 0)
{
printk(KERN_ALERT "设备文件无数据!\n");
return ‐1;
}
if (*f_pos >= p_myDev‐>size)
{
printk(KERN_ALERT "已到文件末尾!\n");
*f_pos = 0;
return ‐2;
}
if (count > p_myDev‐>size ‐ (*f_pos))
count = p_myDev‐>size ‐ (*f_pos);
err = copy_to_user(buff, (void *)(p_myDev‐>buff + (*f_pos)), count);
if (err)
{
printk(KERN_ALERT "读取数据失败!\n");
return ‐EFAULT;
}
else
printk(KERN_ALERT "读取数据:%d Byte\n", count);
*f_pos += count;
printk(KERN_ALERT "读取数据后设备文件大小:%lu Byte\n", p_myDev‐>size);
printk(KERN_ALERT "读取数据后文件指针位置:%lld\n", *f_pos);
return count;
}
//实现 write 函数 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
ssize_t myWrite(struct file *p_file, const char __user *buff, size_t count, loff_t *f_pos)
{
int err;
struct Char_dev *p_myDev;
p_myDev = p_file‐>private_data;
printk(KERN_ALERT "写入数据前设备文件大小:%lu Byte\n", p_myDev‐>size);
printk(KERN_ALERT "写入数据前文件指针位置:%lld\n", *f_pos);
if ((*f_pos) >= MAXSIZE)
if (count + (*f_pos) > MAXSIZE)
count = MAXSIZE ‐ (*f_pos);
err = copy_from_user(p_myDev‐>buff + (*f_pos), buff, count);
if (err)
{
printk(KERN_ALERT "写入数据失败!\n");
return ‐EFAULT;
}
else
printk(KERN_ALERT "写入数据:%d Byte\n", count);
*f_pos += count;
if ((*f_pos) > p_myDev‐>size)
p_myDev‐>size = (*f_pos);
printk(KERN_ALERT "写入数据后设备文件大小:%lu Byte\n", p_myDev‐>size);
printk(KERN_ALERT "写入数据后文件指针位置:%lld\n", *f_pos);
return count;
}
//实现文件定位 llseek 函数‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
loff_t myLlseek(struct file *p_file, loff_t offset, int modu)
{
loff_t newpos;
struct Char_dev *p_myDev = p_file‐>private_data;
switch (modu)
{
case SEEK_CUR:
newpos = p_file‐>f_pos + offset;
break;
case SEEK_END:
newpos = p_myDev‐>size ‐ 1 + offset;
break;
case SEEK_SET:
newpos = offset;
break;
default:
newpos = ‐1;
break;
}
if (p_myDev‐>size < newpos)
newpos = p_myDev‐>size;
p_file‐>f_pos = newpos;
printk(KERN_ALERT "文件位置设为:%lld", newpos);
return newpos;
}