当前位置: 首页 > article >正文

Linux驱动开发学习笔记1《字符设备驱动开发》

目录

一、字符设备驱动简介

二、chrdevbase 字符设备驱动开发实验 

1.创建驱动程序的目录

2.创建vscode工程 

3.编写实验程序

4.编译驱动程序和测试APP代码

(1)加载驱动模块

(2)创建设备节点文件

(3)chrdevbase 设备操作测试

(4)卸载驱动模块


一、字符设备驱动简介

        字符设备是Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

二、chrdevbase 字符设备驱动开发实验 

        本节我们就以chrdevbase 这个虚拟设备为例,完整的编写一个字符设备驱动模块。chrdevbase 不是实际存在的一个设备,是为了方便讲解字符设备的开发而引入的一个虚拟设备。chrdevbase 设备有两个缓冲区,一个为读缓冲区,一个为写缓冲区,这两个缓冲区的大小都为100 字节。在应用程序中可以向chrdevbase 设备的写缓冲区中写入数据,从读缓冲区中读取数据。chrdevbase 这个虚拟设备的功能很简单,但是它包含了字符设备的最基本功能。

1.创建驱动程序的目录

2.创建vscode工程 

 将工作区另存为chrdevbase

使用命令code . 进入vscode

因为是编写Linux 驱动,因此会用到Linux 源码中的函数。我们需要在VSCode 中添加Linux
源码中的头文件路径。打开VSCode,按下“Crtl+Shift+P”打开VSCode 的控制台,然后输入
“C/C++: Edit configurations(UI) ”,打开C/C++编辑配置文件,如下图所示:

 

打开以后会自动在.vscode 目录下生成一个名为c_cpp_properties.json 的文件,此文件修改为如下所示如下所示: 

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/ssz/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include",
                "/home/ssz/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include",
                "/home/ssz/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}

上面includePath 表示头文件路径,需要将Linux 源码里面的头文件路径添加进来,也就是我们前面移植的Linux 内核源码中的头文件路径。

3.编写实验程序

创建chrdevbase.c驱动程序,代码如下:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>


#define CHRDEVBASE_MAJOR 200 //主设备号
#define CHRDEVBASE_NAME "chrdevbase" //设备名

static char readbuf[100]; //读缓冲区
static char writebuf[100]; //写缓冲区
static char kerneldata[] = {"kernel data!"};

//打开设备
static int chardevbase_open(struct inode *inode, struct file *filp)
{
    printk("chrdevbase open!\r\n");
    return 0;
}
//向设备读取数据
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off_t)
{
    int retvalue = 0;

    //向用户空间发送数据
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    //内核到用户
    retvalue = copy_to_user(buf, readbuf, cnt);
    if(retvalue == 0)
    {
        printk("kernel sendata ok!\r\n");
    }
    else
    {
        printk("kernel senddata failed!\r\n");
    }

    //printk("chrdevbase read!\r\n");
    return 0; 
}
//向设备写数据
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    //接收用户空间传递给内核的数据并且打印出来
    //用户到内核
    retvalue = copy_from_user(writebuf, buf, cnt);
    if(retvalue == 0)
    {
        printk("kernel recevdata:%s\r\n",writebuf);
    }
    else
    {
        printk("kernel recedata failed!\r\n");
    }

    //printk("chrdevbase write!\r\n");
    return 0;
}
//关闭/释放设备
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    //printk("chrdevbase release!\r\n");
    return 0;
}


static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,
    .open = chardevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};



static int __init chrdevbase_init(void)
{
    
    int retvalue = 0;

    //注册字符设备驱动
    retvalue = register_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME,&chrdevbase_fops);
    if(retvalue < 0)
    {
        //字符设备注册失败,自行处理
        printk("chrdevbase driver register failed\r\n");
    }
    printk("chrdevbase_init()\r\n");
    return 0;
}
static void __exit chrdevbase_exit(void)
{
    //注销字符设备驱动
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
    printk("chrdevbase_exit()\r\n");
}
/*
驱动模块入口与出口
*/
module_init(chrdevbase_init);//入口
module_exit(chrdevbase_exit);//出口

//LICENSE和作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ssz");

创建chrdevbaseApp.c测试程序,代码如下:

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

static char usrdate[] = {"usr data!"};

int main(int argc, char *argv[])
{
    int fd,retvalue;
    char *filename;
    char readbuf[100],writebuf[100];

    if(argc != 3)
    {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    //打开驱动文件
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("Cant't open file %s\r\n",filename);
        return -1;
    }
    if(atoi(argv[2]) == 1)
    {
        //从驱动文件读取数据
        retvalue = read(fd, readbuf, 50);
        if(retvalue < 0)
        {
            printf("read file %s failed!\r\n",filename);
        }
        else
        {
            //读取成功,打印出读取成功的数据
           // printf("1111\n");
            printf("read data: %s \r\n",readbuf);
        }  
    }
    if(atoi(argv[2]) == 2)
    {
        //向设备驱动写数据
        memcpy(writebuf, usrdate, sizeof(usrdate));
        retvalue = write(fd, writebuf, 50);
        if(retvalue < 0)
        {
            printf("write file %s failed!\r\n",filename);
        }
    }
    retvalue = close(fd);
    if(retvalue < 0)
    {
        printf("Can't close file %s\r\n",filename);
        return -1;
    }
    return 0;
}

编译驱动程序,也就是chrdevbase.c 这个文件,我们需要将其编译为.ko 模块,创建
Makefile 文件,然后在其中输入如下内容:

KERNELDIR := /home/ssz/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o

build : kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

4.编译驱动程序和测试APP代码

编译驱动程序,输入make命令,会生成chrdevbase.ko文件:

编译测试APP:

使用file命令,chrdevbaseApp这个可执行文件是32 位LSB 格式,ARM 版本的,因此chrdevbaseApp只能在ARM 芯片下运行。

 运行测试:

(1)加载驱动模块

驱动模块chrdevbase.ko 和测试软件chrdevbaseAPP 都已经准备好了,接下来就是运行测试。为了方便测试,Linux 系统选择通过TFTP 从网络启动,并且使用NFS 挂载网络根文件系统,确保uboot 中bootcmd 和bootargs环境变量的值为(确保电脑与开发板通过网线连接):

setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000'
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.66:/home/ssz/linux/nfs/
rootfs ip=192.168.1.66:192.168.1.55:192.168.1.1:255.255.255.0::eth0:off'
saveenv

设置好以后启动Linux 系统,检查开发板根文件系统中有没有“/lib/modules/4.1.15”这个目录,如果没有的话自行创建。注意,“/lib/modules/4.1.15”这个目录用来存放驱动模块,使用modprobe 命令加载驱动模块的时候,驱动模块要存放在此目录下。“/lib/modules”是通用的,不管你用的什么板子、什么内核,这部分是一样的。不一样的是后面的“4.1.15”,这里要根据你所使用的Linux 内核版本来设置,比如ALPHA 开发板现在用的是4.1.15 版本的Linux 内核,因此就是“/lib/modules/4.1.15”。如果你使用的其他版本内核,比如5.14.31,那么就应该创建“/lib/modules/5.14.31”目录,否则modprobe 命令无法加载驱动模块。因为是通过NFS 将Ubuntu 中的rootfs(第三十八章制作好的根文件系统)目录挂载为根文件系统,所以可以很方便的将chrdevbase.ko 和chrdevbaseAPP 复制到rootfs/lib/modules/4.1.15 目录中,命令如下:

拷贝完成以后就会在开发板的/lib/modules/4.1.15 目录下存在chrdevbase.ko 和chrdevbaseAPP 这两个文件,如图所示: 

输入如下命令加载chrdevbase.ko 驱动文件: 

insmod chrdevbase.ko
或者
modprobe chrdevbase.ko

从上图 可以看出,modprobe 提示无法打开“modules.dep”这个文件,因此驱动挂载失败了。我们不用手动创建modules.dep 这个文件,直接输入depmod 命令即可自动生成modules.dep,有些根文件系统可能没有depmod 这个命令,如果没有这个命令就只能重新配置busybox,使能此命令,然后重新编译busybox。输入“depmod”命令以后会自动生成modules.alias、modules.symbols 和modules.dep 这三个文件,如下图所示:

驱动加载成功:

输入“lsmod”命令即可查看当前系统中存在的模块,结果如下图所示: 

 

从上图可以看出,当前系统只有“chrdevbase”这一个模块。输入如下命令查看当前系统中有没有chrdevbase 这个设备:

从上图可以看出,当前系统存在chrdevbase 这个设备,主设备号为200,跟我们设置
的主设备号一致。 

(2)创建设备节点文件

驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/chrdevbase 这个设备节点文件: 

其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个字符设备,“200”是设备的主设备号,“0”是设备的次设备号。创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用“ls /dev/chrdevbase -l”命令查看,结果如上图所示:

如果chrdevbaseAPP 想要读写chrdevbase 设备,直接对/dev/chrdevbase 进行读写操作即可。相当于/dev/chrdevbase 这个文件是chrdevbase 设备在用户空间中的实现。前面一直说Linux 下一切皆文件,包括设备也是文件, 

(3)chrdevbase 设备操作测试

读写操作测试:

 

 

(4)卸载驱动模块

如果不再使用某个设备的话可以将其驱动卸载掉,比如输入如下命令卸载掉chrdevbase 这个设备:  

从上图可以看出,此时系统已经没有任何模块了,chrdevbase 这个模块也不存在了,说明模块卸载成功。至此,chrdevbase 这个设备的整个驱动就验证完成了,驱动工作正常。


http://www.kler.cn/a/154725.html

相关文章:

  • 计算2的N次方
  • DAY65||Bellman_ford 队列优化算法(又名SPFA)|bellman_ford之判断负权回路|bellman_ford之单源有限最短路
  • 我的docker随笔45:在龙芯平台安装docker
  • w~视觉~合集23
  • ES6更新的内容中什么是proxy
  • Essential Cell Biology--Fifth Edition--Chapter one (6)
  • VT-VRPA2-1-1X/V0/T5控制4WRE6比例方向阀放大板
  • Wordpress自动定时发布怎么开通-Wordpress怎么自动发布原创文章
  • 4-Docker命令之docker pause
  • react-native实践日记--6.ReactNative 项目版本升级,0.61到0.72升级的问题记录(二)
  • 锂电涂布机设备健康管理:降低运维成本的关键
  • pygame实现贪吃蛇小游戏
  • mybatis项目中添加logback日志
  • Android 13.0 默认授予app获取序列号SerialNo权限
  • SQL Server 2016(分离和附加数据库)
  • 【计算机网络笔记】802.11无线局域网
  • 深度学习【二】
  • [二分查找]LeetCode2009 :使数组连续的最少操作数
  • 数据管理系统-week10-自由访问控制
  • JavaSE自定义验证码图片生成器
  • Frida hook框架环境搭建
  • 【题目】链表相关算法题
  • Beta冲刺总结随笔
  • 论文编写软件latex安装教程
  • Linux: 退出vim编辑模式
  • Scrapy框架内置管道之图片视频和文件(一篇文章齐全)