0


驱动开发(六):应用层通过文件系统与内核层交互

驱动开发相关文章:

驱动开发(一):驱动代码的基本框架

驱动开发(二):创建字符设备驱动

驱动开发(三):内核层控制硬件层

驱动开发(四):Linux内核中断

驱动开发(五):Linux内核定时器

驱动开发(六):应用层通过文件系统与内核层交互

驱动开发(应用):设计一个社区饮水机控制系统

应用层与内核层的交互方式

在Linux驱动开发中,应用层可以通过以下几种方式来控制内核层:

  1. 文件系统接口:应用程序可以通过文件系统接口来与内核驱动进行通信。应用程序可以通过打开、读取、写入和关闭文件等操作来发送控制命令或获取设备状态。
  2. 系统调用:应用程序可以通过系统调用来请求内核执行某些操作。例如,应用程序可以使用ioctl()系统调用来发送特定的命令给内核驱动。
  3. proc文件系统:proc文件系统提供了一种特殊的文件系统接口,允许应用程序通过读写虚拟文件来与内核进行通信。应用程序可以通过读写/proc文件系统中的特定文件来发送控制命令或获取设备状态。
  4. sysfs文件系统:sysfs文件系统是Linux内核提供的一种特殊文件系统,用于向用户空间提供硬件设备的信息。应用程序可以通过读写sysfs文件来控制设备的行为。
  5. netlink套接字:Netlink是一种用于内核与用户空间之间进行通信的机制。应用程序可以通过创建和使用Netlink套接字来与内核驱动进行通信。

这些方式都可以让应用层与内核层进行交互和控制,具体选择哪种方式取决于应用的需求和设备的特性。

通过文件系统交互

在Linux驱动开发中,通过文件系统进行应用层和内核层的交互涉及以下几个步骤:

  1. 定义设备节点:在内核驱动中,需要定义设备节点,即创建一个特殊的文件。这可以通过调用 register_chrdev()函数来完成。该函数会分配一个主设备号和次设备号,并创建对应的设备节点。
  2. 实现设备文件操作函数:在内核驱动中,需要实现设备文件操作函数,包括 open()read()write()release()等。这些函数会在应用程序对设备文件执行相应操作时被内核调用。
  3. 注册设备文件操作函数:通过调用 file_operations 结构体中的相应函数指针来注册设备文件操作函数,这样内核就会在对设备文件执行相应操作时调用相应的函数。
  4. 用户空间操作设备文件:在应用层,应用程序可以通过文件系统接口(如open()、read()、write()和close()等函数)来操作设备文件。例如,应用程序可以使用open()函数打开设备文件,使用write()函数向设备发送命令,使用read()函数读取设备的状态,并使用close()函数关闭设备文件。
  5. 内核层处理设备文件操作:当应用程序调用文件系统接口函数时,内核会相应地调用对应的设备文件操作函数。在这些设备文件操作函数中,可以对设备进行相应处理,如处理读写请求、控制设备的操作等。

通过以上步骤,应用层和内核层就可以通过文件系统进行交互了。应用程序通过操作设备文件,触发设备文件操作函数的调用,从而实现与内核驱动的通信和控制。

file_operations 结构体

file_operations

结构体是Linux内核中用于注册设备文件操作函数的重要结构体。它定义在 <

linux/fs.h

头文件中。

file_operations

结构体可以在内核驱动中使用,将设备文件操作函数与对应的设备文件关联起来。

file_operations

结构体的常用函数及注释如下:

struct file_operations {
    struct module *owner; // 拥有此结构体的模块的指针,用于管理模块的引用计数

    loff_t (*llseek) (struct file *, loff_t, int);
    // 设置文件指针位置的函数,可用于在读写文件时跳过一定的字节数
    // 参数:
    //   - struct file *:文件描述符指针
    //   - loff_t:偏移量,表示需要移动的字节数
    //   - int:位置标志,用于确定移动的起始位置(SEEK_SET,SEEK_CUR,SEEK_END)
    // 返回值:
    //   - loff_t:调整后的文件指针位置

    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    // 读取设备文件内容的函数,在read系统调用中被调用
    // 参数:
    //   - struct file *:文件描述符指针
    //   - char __user *:用户空间缓冲区指针,用于存储读取的数据
    //   - size_t:读取的数据大小
    //   - loff_t *:文件指针位置的指针,用于更新读取后的文件指针位置
    // 返回值:
    //   - ssize_t:实际读取的数据大小,若失败则返回错误码

    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    // 写入数据到设备文件的函数,在write系统调用中被调用
    // 参数:
    //   - struct file *:文件描述符指针
    //   - const char __user *:用户空间缓冲区指针,包含要写入的数据
    //   - size_t:要写入的数据大小
    //   - loff_t *:文件指针位置的指针,用于更新写入后的文件指针位置
    // 返回值:
    //   - ssize_t:实际写入的数据大小,若失败则返回错误码

    int (*open) (struct inode *, struct file *);
    // 打开设备文件时被调用的函数,在open系统调用中被调用
    // 参数:
    //   - struct inode *:文件的inode节点指针
    //   - struct file *:文件描述符指针
    // 返回值:
    //   - int:成功返回0,否则返回错误码

    int (*release) (struct inode *, struct file *);
    // 关闭设备文件时被调用的函数,在close系统调用中被调用
    // 参数:
    //   - struct inode *:文件的inode节点指针
    //   - struct file *:文件描述符指针
    // 返回值:
    //   - int:成功返回0,否则返回错误码

    // 其他成员函数...
};

除了上述函数外,

file_operations

结构体还包括其他一些函数指针,如

poll

ioctl

mmap

等,用于实现更高级的设备文件操作。

在内核驱动中,可以通过定义一个自定义的

file_operations

结构体,并将其与设备文件关联起来,以注册设备文件操作函数。这样,在应用程序通过文件系统接口操作设备文件时,相应的设备文件操作函数将会被内核调用,实现设备的处理和控制。例如,可以通过调用

cdev_init()

函数将自定义的

file_operations

结构体注册到字符设备驱动中。

应用程序将数据传递给驱动

从用户空间拷贝数据到内核空间

#include <linux/uaccess.h>
int copy_from_user(void *to, const void __user *from, int n)
    功能:从用户空间拷贝数据到内核空间
    参数:
        @to  :内核中内存的首地址
        @from:用户空间的首地址
        @n   :拷贝数据的长度(字节)
    返回值:成功返回0,失败返回未拷贝的字节的个数

从内核空间拷贝数据到用户空间

int copy_to_user(void __user *to, const void *from, int n)
    功能:从内核空间拷贝数据到用户空间
    参数:
        @to  :用户空间内存的首地址
        @from:内核空间的首地址 __user需要加作用是告诉编译器这是用户空间地址
        @n   :拷贝数据的长度(字节)
    返回值:成功返回0,失败返回未拷贝的字节的个数

示例代码

驱动代码(hello.c)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define CNAME "hello"
int copy_size = 0;
char kbuf[128] = {0};
unsigned int major = 0;
ssize_t mycdv_read(struct file *file, char __user *ubuf, size_t len, loff_t *loff)
{
    if (len > sizeof(kbuf))    //len是用户需要的,有可能大于内核传的
    {
        len = sizeof(kbuf);    //大于的时候,有多少就给它多少
    }
    copy_size = copy_to_user(ubuf, kbuf, len);
    if (copy_size)
    {
        printk(KERN_ERR "copy_to_user error\n");
        return copy_size;
    }
    return 0;
}
ssize_t mycdv_write(struct file *file, const char __user *ubuf, size_t len, loff_t *lo)
{
    if (len > sizeof(kbuf))
    {
        len = sizeof(kbuf);
    }
    copy_size = copy_from_user(kbuf, ubuf, len);
    if (copy_size)
    {
        printk(KERN_ERR "copy_from_user error\n");
        return copy_size;
    }
    return 0;
}
int mycdv_open(struct inode *inode, struct file *file)
{
    printk(KERN_ERR "mycdv open ok\n");
    return 0;
}
int mycdv_release(struct inode *inode, struct file *file)
{
    printk(KERN_ERR "mycdv release ok\n");
    return 0;
}

const struct file_operations fops =
    {
        .read = mycdv_read,
        .write = mycdv_write,
        .open = mycdv_open,
        .release = mycdv_release,
};

static int __init hello_init(void)
{
    major = register_chrdev(major, CNAME, &fops);
    if (major < 0)
    {
        printk(KERN_ERR "register chrdev ERR\n");
        return major;
    }
    return 0;
}
static void __exit hello_exit(void)
{
    unregister_chrdev(major, CNAME);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
应用层(main.c)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

char buf[128]="this is my_cdev";
int main(int argc, char const *argv[])
{
    int fd;
    fd = open("./hello",O_RDWR);
    if(fd<0)
    {
        perror("open err");
        return -1;
    }
    write(fd,buf,sizeof(buf));
    memset(buf,0,sizeof(buf));
    read(fd,buf,sizeof(buf));
    printf("这是读出来的:%s\n",buf);
    close(fd);
    return 0;
}

本文转载自: https://blog.csdn.net/a1547998353/article/details/139698495
版权归原作者 爱做梦的小涛 所有, 如有侵权,请联系我们删除。

“驱动开发(六):应用层通过文件系统与内核层交互”的评论:

还没有评论