一个简单的Linux设备驱动程序

本文深入探讨了Linux设备驱动程序的工作原理,并详细介绍了编写一个简单的字符设备驱动程序的完整流程。从获取设备号、注册设备驱动、关联系统调用到释放设备号,每个步骤都进行了详细说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我所理解的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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值