Linux Kernel Driver 字符设备驱动 续
程序员文章站
2024-02-29 12:44:22
...
前文
hello 字符设备已经完成了open以及close的操作,但是
第一,不能读写
第二,并发不安全
读写
我们这是字符设备,但是是假的,没有硬件支持,所以,我们写进来的东西,还要能读出去, 我们最好还能简单处理一下,模拟一下硬件设备,我们用个全局的int来存储,模拟一下硬件的状态。
应用层通过系统调用 write / read 进行操作
内核层通过注册对应的 struct file_operations 的对应的op callback, read跟write
需要特别说明:
1. __user ,干嘛的?该部分的变量保存的是用户缓冲区的首地址,内核代码是不允许直接访问该地址指向的内存的,如果驱动想要用用户缓冲区中写入数据,需要利用内核提供的内存copy函数即可完成数据写入/读取操作。
2. read 的 loff_t * ppos 参数
记录上一次的读位置信息,代码操作如下:
首先获取上一次的读位置信息
loff_t pos = *ppos;
这次读了2字节,读完以后,要记得更新读位置
*ppos = pos + 2;
此形参一般用于多次读!
3. write 的 loff_t * ppos 参数
保存上一次的写位置,操作如下:
首先获取上一次的写位置
loff_t pos = *ppos;
每一次写入12字节,更新写位置信息
*ppos = pos + 12
此参数适合多次写入操作
4. 其实open 与 release 接口,可以不用指定初始化,应用程序永远返回成功。
代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> // struct file_operations
#include <linux/cdev.h> // struct cdev
#include <linux/uaccess.h>
MODULE_DESCRIPTION("Frocheng: Driver for DEMO!");
MODULE_AUTHOR("Frodo Cheng");
MODULE_LICENSE("GPL");
MODULE_VERSION("V0.0.1");
static char* name = "hello";
static int state;
// 应用程序调用关系:open->软中断->sys_open->hello_open
static int hello_open(struct inode *inode,
struct file *file)
{
printk("===[frocheng]===[%s]===[%s]===[%d]===\n",__FILE__, __func__, __LINE__);
return 0; // 执行成功返回0,失败返回负值
}
// 应用程序调用关系:close->软中断->sys_close->hello_close
static int hello_close(struct inode *inode,
struct file *file)
{
printk("===[frocheng]===[%s]===[%s]===[%d]===\n",__FILE__, __func__, __LINE__);
return 0; //执行成功返回0,失败返回负值
}
// 应用read->软中断->sys_read->hello_read
static ssize_t hello_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
// 将内核缓冲区的数据拷贝到用户缓冲区
copy_to_user(buf, &state, sizeof(state));
return sizeof(state); // 返回实际读取的字节数
}
// 应用write->软中断->sys_write->hello_write
static ssize_t hello_write(struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos)
{
int m = 0; // 内核区域的 对象
copy_from_user(&m, buf, sizeof(m));
// 模拟操作一下:
// 如果写进来的是偶数,那么,内部状态 更新为 m * 2;
// 如果写进来的是计数,那么,内部状态 更新为 m * 3;
if ((m & 0x01) == 0x00)
{
state = m * 2;
}
else
{
state = m * 3;
}
return sizeof(m);
}
// 定义初始化LED的硬件操作对象
// open,release一旦加载内存中,静静等待着应用程序来调用
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open, // 打开设备
.release = hello_close, // 关闭设备
.write = hello_write,
.read = hello_read
};
// 定义字符设备对象
static struct cdev hello_cdev;
// 定义设备号对象
static dev_t dev;
static int __init hello_init(void)
{
int rc = -1;
printk("===[frocheng]===[%s]===[%s]===[%d]===[Hello !]===\n",__FILE__, __func__, __LINE__);
// 申请设备号
rc = alloc_chrdev_region(&dev, 0, 1, name);
if (rc != 0)
{
printk("===[frocheng]===[%s]===[%s]===[alloc_chrdev_region error with %d]===\n",__FILE__, __func__, rc);
// hello 驱动模块加载失败,因为并没有申请到字符神资源,驱动加载失败。
return -1;
}
printk("===[frocheng]===[%s]===[%s]===[name=%s, major=%u, minor=%u]===\n",__FILE__, __func__, name, MAJOR(dev), MINOR(dev));
// 初始化字符设备对象
cdev_init(&hello_cdev, &hello_fops);
// 注册字符设备对象
cdev_add(&hello_cdev, dev, 1);
return 0;
}
static void __exit hello_exit(void)
{
// 卸载字符设备对象
cdev_del(&hello_cdev);
// 释放设备号
unregister_chrdev_region(dev, 1);
printk("===[frocheng]===[%s]===[%s]===[%d]===[Bye bye...]===\n",__FILE__, __func__, __LINE__);
}
module_init(hello_init);
module_exit(hello_exit);
测试代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd;
// open->软中断->sys_open->hello_open
fd = open("/dev/hello", O_RDWR);
if (fd < 0)
{
perror("open");
return -1;
}
int state = 0;
read(fd, &state, sizeof(state));
printf("Get initial state = %d\n", state);
state = 3;
printf("Set state = %d\n", state);
write(fd, &state, sizeof(state));
read(fd, &state, sizeof(state));
printf("Get state = %d\n", state);
state = 2;
printf("Set state = %d\n", state);
write(fd, &state, sizeof(state));
read(fd, &state, sizeof(state));
printf("Get state = %d\n", state);
// close->软中断->sys_close->hello_close
close(fd);
return 0;
}
结果
上一篇: 轻松解决asp.net用户ASPNET登录失败问题的方法分享
下一篇: P1014 Cantor表