欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux Kernel Driver 字符设备驱动 续

程序员文章站 2024-02-29 12:44:22
...

前文

hello 字符设备已经完成了open以及close的操作,但是

第一,不能读写

第二,并发不安全

读写

我们这是字符设备,但是是假的,没有硬件支持,所以,我们写进来的东西,还要能读出去, 我们最好还能简单处理一下,模拟一下硬件设备,我们用个全局的int来存储,模拟一下硬件的状态。

应用层通过系统调用  write / read 进行操作

内核层通过注册对应的 struct file_operations 的对应的op callback, read跟write

Linux Kernel Driver 字符设备驱动 续

需要特别说明:

1. __user ,干嘛的?该部分的变量保存的是用户缓冲区的首地址,内核代码是不允许直接访问该地址指向的内存的,如果驱动想要用用户缓冲区中写入数据,需要利用内核提供的内存copy函数即可完成数据写入/读取操作。

Linux Kernel Driver 字符设备驱动 续

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;
}

结果

Linux Kernel Driver 字符设备驱动 续