Linux嵌入式驱动开发04——应用层和内核层数据传输

全系列传送门

Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)

Linux嵌入式驱动开发02——驱动编译到内核

Linux嵌入式驱动开发03——杂项设备驱动(附源码)

Linux嵌入式驱动开发04——应用层和内核层数据传输

Linux嵌入式驱动开发05——物理地址到虚拟地址映射

Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写

Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)

Linux嵌入式驱动开发08——字符设备(步步为营)

Linux嵌入式驱动开发09——平台总线详解及实战

Linux嵌入式驱动开发10——设备树开发详解

Linux嵌入式驱动开发11——平台总线模型修改为设备树实例

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)

Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

Linux嵌入式驱动开发15——等待队列和工作队列

Linux嵌入式驱动开发16——按键消抖实验(内核定时器)

Linux嵌入式驱动开发17——输入子系统

Linux嵌入式驱动开发18——I2C通信

引言

对于Linux,一切都可以是文件

文件为对应的操作有打开,关闭, 读写

那么

设备节点对应的操作有打开,关闭,读写

如果我在应用层使用系统IO对设备节点进行打开,关闭,读写操作,会发生什么呢?

应用层和内核层关系

开发板的Linux的源码目录下,找到inode_operations结构体定义

include/linux/fs.h

file_operations 文件集

通过搜索查找file_operations 可以找到这个结构体

struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
        int (*iterate) (struct file *, struct dir_context *);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*mremap)(struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **, void **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
        void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
        unsigned (*mmap_capabilities)(struct file *);
#endif
};

下面对常见的几个函数来进行介绍

read

对于文件集结构体中,我们来看这个read函数

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

当我们在应用层read设备节点的时候,就会触发我们驱动里面的read这个函数

write

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

当我们在应用层write设备节点的时候,就会触发我们驱动里面的write这个函数

poll

unsigned int (*poll) (struct file *, struct poll_table_struct *);

当我们在应用层poll/select设备节点的时候,就会触发我们驱动里面的poll/select这个函数

iocontrol

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

当我们在应用层ioctl设备节点的时候,就会触发我们驱动里面的ioctl这个函数

open

int (*open) (struct inode *, struct file *);

当我们在应用层open设备节点的时候,就会触发我们驱动里面的open这个函数

close

int (*release) (struct inode *, struct file *);

当我们在应用层close设备节点的时候,就会触发我们驱动里面的close这个函数

驱动例子

Linux嵌入式应用层和内核层数据传输modules_file_operations

file_operations modules

代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>

int misc_open (struct inode *inode, struct file *file){
    
    printk("hello misc_open!!!\n");
    return 0;
}

int misc_release(struct inode *inode, struct file *file){

    printk("bye bye misc_release!!!\n");

    return 0;

}

ssize_t misc_read(struct file *file, const char __user *user, size_t size, loff_t *loff_t){
    
    printk("hello misc_read!!!\n");

    return 0;
}

ssize_t misc_write(struct file *file, const char __user *user, size_t size, loff_t *loff_t){
    
    printk("hello misc_write!!!\n");

    return 0;
}

struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
};

struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops
};


static int misc_init(void)
{
    int ret;

    ret = misc_register(&misc_dev);

    if(ret < 0){
        printk("misc_register failed!!!\n");
        return -1;
    }

    printk("misc_register succeed!!!\n");          // 在内核中无法使用c语言库,所以不用printf
    
    return 0;
}

static void misc_exit(void)
{
    misc_deregister(&misc_dev);

    printk("misc exit!!!\n");
}

module_init(misc_init);
module_exit(misc_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

这里的打印使用的是printk,因为这个是在内存中打印,所以不是常见的printf

编译 加载模块

编译后,安装模块到开发板
在这里插入图片描述
查看模块列表

lsmod

在这里插入图片描述
查看设备节点有没有问题

ls /dev

可以看到我们自己写的驱动模块
在这里插入图片描述

app

有了驱动代码,下面我们还需要的就是应用层的代码,来实现我们要实现的一些功能。

代码

这个代码主要就是调用了read和write,然后看看能不能正常的使用

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;

    char buff[64] = {0};

    fd = open("/dev/hello_misc", O_RDWR);

    if(fd < 0){

        perror("open error\n");               // 在应用中打印
        return fd;
    }

    read(fd, buff, sizeof(buff));

    write(fd, buff,sizeof(buff));
    
    close(fd);

    return 0;
}

perror是在应用中的打印函数

编译

对于飞凌的开发板,不能直接使用

arm-poky-linux-gnueabi-gcc app.c -o app

因为要有环境变量的配置,但是飞凌给我们写好了

查看 /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setuo-cortexa9hf-neon-poky-linux-gnueabi 文件可以发现在大约11行的地方export CC=”arm-poky-linux-gnueabi-gcc -march=armv7-a…“
在这里插入图片描述

所以,gcc的参数已经声明成环境变量CC了,所以对于飞凌的这个开发板,只需要执行$CC,就相当于执行了一系列的环境配置arm-poky-linux-gnueabi-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a9 --sysroot=$SDKTARGETSYSROOT

实际操作如下

$CC app.c -o app

在这里插入图片描述
全部编译好之后下载到开发板

scp app root@192.168.0.232:/tmp

验证

来到我们的开发板上

确保我们的file_operations驱动加载成功后,执行app可执行文件
在这里插入图片描述

小结

我们上面的过程,可以总结成这个框图
在这里插入图片描述
中间的设备接地那就是应用层和内核层的桥梁

  • 如果我们的file_operations里面没有read,我们在应用层read设备节点的时候会发生什么?

  • 答案是什么也不会发生,也不会报错

上面的这套过程,就编写了一个的驱动模块,主要用到的就是file_operations文件集的函数

应用层和内核层数据层传输

应用层和内核层不能直接数据层传输

这两个函数,只可以在驱动中使用,来完成我们的应用层和应用层的桥梁功能

static inline long copy_from_user(void *to, const void _user *from, unsigned long n)

static inline long copy_to_user(void _user *to, const void *from, unsigned long n)

read功能实践

file_operations.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

int misc_open (struct inode *inode, struct file *file){
    
    printk("hello misc_open!!!\n");
    return 0;
}

int misc_release(struct inode *inode, struct file *file){

    printk("bye bye misc_release!!!\n");

    return 0;

}

ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    
    char kbuf[64] = "copy to user!!!\n";

    if( copy_to_user(ubuf, kbuf, size) != 0 ){
        printk("copy_to_user error!!!\n");
        return -1;
    }

    printk("hello misc_read!!!\n");

    return 0;
}

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    printk("hello misc_write!!!\n");

    return 0;
}

struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
};

struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops
};


static int misc_init(void)
{
    int ret;

    ret = misc_register(&misc_dev);

    if(ret < 0){
        printk("misc_register failed!!!\n");
        return -1;
    }

    printk("misc_register succeed!!!\n");          // 在内核中无法使用c语言库,所以不用printf
    
    return 0;
}

static void misc_exit(void)
{
    misc_deregister(&misc_dev);

    printk("misc exit!!!\n");
}

module_init(misc_init);
module_exit(misc_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

这段代码的核心也就是下面这一段,我们可以看到copy_to_user(ubuf, kbuf, size)

这样在应用层调用read来读取驱动模块的时候,就可以启动misc_read函数来执行操作,把内核层的数据kbuf发送到应用层的接口

ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    
    char kbuf[64] = "copy to user!!!\n";

    if( copy_to_user(ubuf, kbuf, size) != 0 ){
        printk("copy_to_user error!!!\n");
        return -1;
    }

    printk("hello misc_read!!!\n");

    return 0;
}

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;

    char buff[64] = "app test copy from user";

    fd = open("/dev/hello_misc", O_RDWR);

    if(fd < 0){

        perror("open error\n");               // 在应用中打印
        return fd;
    }

    read(fd, buff, sizeof(buff));
    printf("buf is:%s\n", buff);
    
    close(fd);

    return 0;
}

read(fd, buff, sizeof(buff));这个就是我们上面说的来启动和接收内核层发送过来的数据的函数,这样就可以把我们内核层中的kbuf数据,发送到了应用层的buff中。

开发板的运行结果如图所示
在这里插入图片描述

write功能实践

file_operations.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

int misc_open (struct inode *inode, struct file *file){
    
    printk("hello misc_open!!!\n");
    return 0;
}

int misc_release(struct inode *inode, struct file *file){

    printk("bye bye misc_release!!!\n");

    return 0;

}

ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    
    char kbuf[64] = "copy to user!!!\n";

    if( copy_to_user(ubuf, kbuf, size) != 0 ){
        printk("copy_to_user error!!!\n");
        return -1;
    }

    printk("hello misc_read!!!\n");

    return 0;
}

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    
    char kbuf[64] = "copy from user!!!\n";

    if( copy_from_user(kbuf, ubuf, size) != 0 ){
        printk("copy_from_user error!!!\n");
        return -1;
    }
    printk("buf is:%s\n", kbuf);
    printk("hello misc_write!!!\n");

    return 0;
}

struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
};

struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops
};


static int misc_init(void)
{
    int ret;

    ret = misc_register(&misc_dev);

    if(ret < 0){
        printk("misc_register failed!!!\n");
        return -1;
    }

    printk("misc_register succeed!!!\n");          // 在内核中无法使用c语言库,所以不用printf
    
    return 0;
}

static void misc_exit(void)
{
    misc_deregister(&misc_dev);

    printk("misc exit!!!\n");
}

module_init(misc_init);
module_exit(misc_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

对于驱动中的misc_write来说,跟上面的misc_read差不多,只是需要注意,这一个写操作,是从应用层的buff来写到内核层的kbuf中,并且使用copy_from_user(kbuf, ubuf, size)

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    
    char kbuf[64] = "copy from user!!!\n";

    if( copy_from_user(kbuf, ubuf, size) != 0 ){
        printk("copy_from_user error!!!\n");
        return -1;
    }
    printk("buf is:%s\n", kbuf);
    printk("hello misc_write!!!\n");

    return 0;
}

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;

    char buff[64] = "app test copy from user";

    fd = open("/dev/hello_misc", O_RDWR);

    if(fd < 0){

        perror("open error\n");               // 在应用中打印
        return fd;
    }

    // read(fd, buff, sizeof(buff));
    write(fd, buff,sizeof(buff));

    // printf("buf is:%s\n", buff);
    
    close(fd);

    return 0;
}

write(fd, buff,sizeof(buff));这个就是我们上面说把应用层的数据发送到内核层数据的函数,这样就可以把我们应用层中的buff数据,发送到了内核中的kbuf中。
在这里插入图片描述

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值