微软交流社区

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 104|回复: 0

linux下字符设备驱动开发的基本框架(linux驱动开发篇)

[复制链接]

1

主题

2

帖子

4

积分

新手上路

Rank: 1

积分
4
发表于 2022-11-26 15:46:15 | 显示全部楼层 |阅读模式
字符设备驱动介绍

字符设备是linux驱动中最基本的一类,占90%以上。
常见的字符设备:点灯、按键、iic、spi、lcd等等
这里有个非常主要的一点就是应用程序和驱动之间的关系:
1.应用程序app是运行在用户空间,而驱动运行在内核空间。二者互相访问的桥梁即是***“系统调用”***。系统调用属于C库的一部分。主要即是open、release、write、read等等
2.应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比
如应用程序中调用了 open 这个函数,那么在驱动程序中也得有一个名为 open 的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数
linux中一切都是文件
驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx“即可对硬件进行操作。
驱动开发的步骤

1.驱动模块的加载和卸载

Linux驱动有两种运行方式,
1.第一种就是将驱动编译进 Linux内核中,这样当Linux内核启动的时候就会自动运行驱动程序。
2.第二种就是将驱动编译成模块(Linux下模块扩展名为.ko),在Linux内核启动以后使用“insmod”命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。(我们选择第二种)
module_init(xxx_init); //注册模块加载函数 ==insmod命令会触发括号里的函数xxx_init
module_exit(xxx_exit); //注册模块卸载函数 ==rmmod指令会触发。。。
当然指令后面跟的是编译好的驱动模块xxx.ko
insmod xxx.ko
2.字符设备注册与注销

xxx_init函数内部就是调用字符设备的注册register_chrdev函数
xxx_exit函数。。。。。。。。。。。unregister_chrdev函数
当驱动模块加载成功后,我们需要注册字符设备
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
major:设备号
name:设备名字
fops(重点来了):指向设备的操作函数集合变量
卸载驱动模块的时候也需要注销掉字符设备
static inline void unregister_chrdev(unsigned int major, const char *name)
3.实现设备的具体操作函数

此部分就是解决上面遗留下的额问题 即是file_operations结构体变量fops
我们主要对它进行初始化:xxx为设备名字这里就是定为chrtest,主要实现设备文件的打开和关闭,还有读写
//打开设备
static int chrtest_open(struct inode* inode,struct file* filep)
{
        /*用户具体实现的功能*/
        return 0;
}
//从设备读数据
static ssize_t chrtest_read(struct file *filep,char __user* buf,size_t cnt,loff_t*  offt)
{
        /*用户具体实现的功能*/
        return 0;
}
//向设备写数据

static ssize_t chrtest_write(struct file* filp,const char __user* buf,
size_t cnt,loff_t* offt)
{
        /*用户具体实现的功能*/
        return 0;
}
//关闭/释放设备
static int chrtest_release(struct inode* inode,struct file* filp)
{
        /*用户具体实现的功能*/
        return 0;
}

static struct file_operations test_fops={
        .owner=THIS_MODULE,
        .open=chrtest_open,
        .read = chrtest_read,
        .write=chrtest_write,
        .release = chrtest_release,
};

//驱动入口函数
static int __init xxx_init(void)
{
        int retvalue=0;
        //注册设备驱动
        retvalue=register_chrdev(200,"chrtest",&test_fops);
        if(retvalue<0)
        {
                //失败处理
        }
        return 0;       
}
//驱动出口函数
static void __exit xxx_exit(void)
{
        //注销字符设备驱动
        unregister_chrdev(200,"chrtest");
}

//操作密令insmode 和 rmmode的来源=========指定驱动的出入口函数
module_init(xxx_init);
module_exit(xxx_exit);

//许可证和作者的信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai"); 4.添加许可证和作者info

MODULE_LICENSE() //添加模块LICENSE 信
MODULE_AUTHOR() //添加模块作者信息
文章福利】小编推荐自己的Linux内核技术交流群:【1143996416】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)


资料直通车:最新Linux内核源码资料文档+视频资料
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
linux设备号

1.设备号的组成

高12位是主设备号,低20位是次设备号
,Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。
2.设备号的分配

静态分配
静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用的
动态分配
而且静态分配设备号很容易带来冲突问题,Linux社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。卸载驱动的时候释放掉这个设备号即可。
注册设备驱动之前,需要申请设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

  • dev:保存申请到的设备号。
  • baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这
  • 些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递增。一般baseminor为0,也就是说次设备号从 0开始。
  • count:要申请的设备号数量。
  • name:设备名字。
注销字符设备之后要释放掉设备号,设备号释放函数如下:
void unregister_chrdev_region(dev_t from, unsigned count)
此函数有两个参数:

  • from:要释放的设备号。
  • count:表示从from开始,要释放的设备号数量。
以一个虚拟字符设备为例子来介绍流程

***字符设备驱动开发的基本步骤我们已经了解了,本节我们就以 chrdevbase 这个虚拟设备为例,完整的编写一个字符设备驱动模块。chrdevbase 不是实际存在的一个设备:chrdevbase 设备有两个缓冲区,一个为读缓冲区,一个为写缓冲区,这两个缓冲区的大小都为 100 字节。在应用程序中可以向 chrdevbase设备的写缓冲区中写入数据,从读缓冲区中读取数据,它包含了字符设备的最基本功能。
1.实验程序的编写

主要是实现 初始化设备的操作函数
//打开设备
static int chrtest_open(struct inode* inode,struct file* filep)
{
        /*用户具体实现的功能*/
        return 0;
}
//从设备读数据
static ssize_t chrtest_read(struct file *filep,char __user* buf,size_t cnt,loff_t*  offt)
{
        /*用户具体实现的功能*/
        return 0;
}
//向设备写数据

static ssize_t chrtest_write(struct file* filp,const char __user* buf,
size_t cnt,loff_t* offt)
{
        /*用户具体实现的功能*/
        return 0;
}
//关闭/释放设备
static int chrtest_release(struct inode* inode,struct file* filp)
{
        /*用户具体实现的功能*/
        return 0;
}

static struct file_operations test_fops={
        .owner=THIS_MODULE,
        .open=chrtest_open,
        .read = chrtest_read,
        .write=chrtest_write,
        .release = chrtest_release,
};

//驱动入口函数
static int __init xxx_init(void)
{
        int retvalue=0;
        //注册设备驱动
        retvalue=register_chrdev(200,"chrtest",&test_fops);
        if(retvalue<0)
        {
                //失败处理
        }
        return 0;       
}
//驱动出口函数
static void __exit xxx_exit(void)
{
        //注销字符设备驱动
        unregister_chrdev(200,"chrtest");
}

//操作密令insmode 和 rmmode的来源=========指定驱动的出入口函数
module_init(xxx_init);
module_exit(xxx_exit);

//许可证和作者的信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai"); 1.实验程序编写

1.创建vscode工程=》2.添加头文件路径
2.编写测试app

c库文件操作 基本函数 open函数、read函数、write函数、close函数
编写测试app程序
此app是用户空间的,主要对驱动设备进行读写
#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 usrdata[]={"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("can't opne file %s\r\n",filename);
                return -1;
        }
       
        if(atoi(argv[2])==1){//为1是读数据
                retvalue=read(fd,readbuf,50);//会把参数fd所指的文件传送50个字节到buf指针所指的内存中
                if(retvalue<0)//fail
                        printf("read file %s failed!\r\n",filename);
                else
                        printf("read data:%s\r\n",readbuf);//successful
        }
       
        if(atoi(argv[2])==2)//为2是写数据
        {
                memcpy(writebuf,usrdata,sizeof(usrdata));
                retvalue=write(fd,writebuf,50);//将writebuf缓冲区的50个字节写入fd指向的文件中
                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;
}3.编写驱动程序和测试app

编译驱动程序
将chrdevbase.c这个文件,编译成.ko模块
主要就是编写makefile文件
/*内核源码目录*/
KERNELDIR := /home/rgd/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编译测试app
由于在开发板上面运行,所以我们需要使用交叉编译器:
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
命令执行之后生成的程序是chrdevbaseApp 这个可执行文件是32位LSB格式
4.最终的运行测试

1.加载模块

  • 启动linux系统 :tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000
  • 确保bootrags环境变量的值为:console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:/home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off
  • 将ubuntu中的rootfs目录挂载为根文件系统 :sudo cp chrdevbase.ko chrdevbaseApp/home/zuozhongkai/linux/nfs/rootfs/lib/modules/4.1.15/ -f
  • 当在开发板上有chrdevbase.ko 和 chrdevbaseApp后,我们加载启动文件:insmod chrdevbswe.ko
2.创建设备节点文件
驱动加载成功后需要在/dev目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备文件来完成对具体设备的操作。

  • 创建/dev/chrdevbase这个设备节点文件:mknod /dev/chrdevbase c 200 0
  • 可以使用“ls /dev/chrdevbase -l”命令查看,有没有生成这个文件
  • 此时chrdevbaseAPP想要读写chrdevbase设备,直接对/dev/chrdevbase进行读写操作即可。
  • 相当于/dev/chrdevbase这个文件是chrdevbase设备在用户空间中的实现
3.chrdevbase设备操作测试
万事俱备,现在开始进行操作

  • 使用命令开始对设备进行读操作
./chrdevbaseApp /dev/chrdevbase 1

  • 使用命令对设备进行写操作
./chrdevbaseApp /dev/chrdevbase 2
4.卸载驱动模块

  • 卸载设备驱动
rmmod chrdevbase.ko

  • 使用命令查看
lsmod
原文链接:https://blog.csdn.net/weixin_47397155/article/details/120659899
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|微软交流社区

GMT+8, 2025-1-9 03:52 , Processed in 0.072571 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表