linux驱动开发 | 第一个字符驱动

一、驱动框架编写

1.编写驱动文件

在这里插入图片描述
打开linux内核,全局搜索module_init函数。
linux内核中已经有了很多厂家写好的驱动模块,所以我们完全可以参考他们的代码。

#include <linux/module.h>
static int __init chrdevbase_init(void)
{

}

static void __exit chrdevbase_exit(void)
{

}

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
如果头文件下面显示红色波浪线,说明头文件未找到。参考第一节内核路径的配置

2.编写makefile文件

KERNELDIR := /home/zys/linux/IMX6ULL/linux/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	
kernel_modules代表具体的编译指令,后面的modules代表编译的是模块
-C表示将当前的工作路径切换到指定的目录中,也就是KERNELDIR目录
M表示模块源码目录,也就是CURRENTDIR
make modules命令中加入M=dir以后程序会自动跳到指定的dir目录中读取模块源码并将其编译为.ko文件

makefile编写好以后,直接在控制台输入make命令进行编译。
在这里插入图片描述
编译成功后,会生成chrdevbase.ko文件

3.运行测试

准备开发板,我用的是正点原子的imx6ull开发板。
首先烧录uboot,先编译uboot
在这里插入图片描述
在这里插入图片描述
编译完成,烧写u-boot.bin到sd卡中。
sudo fdisk -l //查看sd卡是哪个设备 /dev/sdd
./imxdownload u-boot.bin /dev/sdd //烧写bin文件到sdd设备中
在这里插入图片描述
烧写完成把sd卡插到开发板上,sd卡启动。

开发板进入uboot之后呢,需要从ubuntu上下载zImage和dtb设备树,通过tftp的方式。
看一下ubuntu中tftp的资源文件夹
在这里插入图片描述
如何让开发板去ubuntu下载指定文件?
首先设置uboot的环境变量,setenv bootcmd

bootcmd=tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-nand.dtb;bootz 80800000 - 83000000
saveenv bootcmd

然后设置bootargs,这里挂载的是网络根文件系统。
在这里插入图片描述

bootargs=console=ttymxc0,115200 rw root=/dev/nfs nfsroot=192.168.2.183:/home/zys/linux/nfs/rootfs ip=192.168.2.157:192.168.2.183:192.168.2.1:255.255.255.0::eth0:off
saveenv bootargs

console=ttymxc0是设置终端,这里是串口,
rw是根文件系统可以读也可以写
nfsroot=ubuntu的ip地址 : nfs的路径
ip=本机ip :服务器ip :网关 : 掩码

bootcmd和bootargs命令设置好以后,输入boot
就会自动调用bootcmd命令,从ubuntu下载系统镜像和设备树
在这里插入图片描述
在这里插入图片描述
根文件系统加载成功。

MobaXterm如何复制?选中就是复制 右键粘贴
MobaXterm如何复制?选中就是复制 右键粘贴

3.linux驱动编写的注意事项。

  1. 编译驱动的时候需要用到linux源码,所以内核源码必须放到在ubuntu中,然后编译内核源码,生成zImage和dtb,设备启动的时候需要从这里下载。

二、加载驱动文件

使用命令 imsmod 或者 modprobe(可以解决依赖)
modprobe命令默认会到 lib/modules/4.1.15 路径下找文件
如果第一次使用modprobe 提示没有这个路径,创建一个即可。

把刚刚编译出来的chrdevbase.ko放到 lib/modules/4.1.15下面

sudo cp chrdevbase.ko /home/zys/linux/nfs/rootfs/lib/modules/4.1.15/ -f

-f 表示替换

modprobe chrdevbase.ko

如果第一次加载模块,需要执行一下depmod命令。提示缺少xx文件,也是需要执行一次这个命令。

查看已经加载了的模块

lsmod

三、卸载驱动

使用命令

rmmod

在这里插入图片描述

四、完善驱动

1.设备号的组成

linux中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成。
主设备号表示某一个具体的驱动。
次设备号表示使用这个驱动的各个设备。

linux提供了一个名为dev_t的数据类型来表示设备号,dev_t定义在include/linux/types.h中

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t		dev_t;

可以看出dev_t是__u32类型,__u32定义在 include/uapi/asm-generic/int-ll64.h中

typedef unsigned int __u32;

其实dev_t就是unsigned int,无符号32位整形。
其中高12位为主设备号,低20位为次设备号。
因此linux系统中主设备号的范围是0-4095,所以在注册设备号的时候不要超过这个范围。

在头文件linux/kdev_t.h中,有一些关于设备号操作的宏

#define MINORBITS	20	//次设备号的位数
#define MINORMASK	((1U << MINORBITS) - 1)	//次设备号的掩码

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))	//获取主设备号
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))	//获取次设备号
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))	//组装设备号 直接赋值给dev_t类型的变量即可

2.设备号的分配

2.1 静态分配设备号
在注册字符设备驱动的时候,我们需要指定一个设备号,比如200。linux内核中已经存在了一些设备号,我们就不能再使用了,所以在注册设备号之前应该看一下此设备号有没有被使用

cat /proc/devices

2.2 动态分配设备号
通过申请设备号来获得,使用如下函数

//参数:
//dev:保存申请到的设备号
//baseminor:次设备号的起始地址,一般为0
//count:申请的设备号的数量,可以连续申请多个,主设备号一样,次设备号不同
//name:设备的名字
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)

注销设备号,注销字符设备之后必须释放掉之前申请的设备号

//from:要释放的设备号
//count:从from开始,要释放的设备号数量
void unregister_chrdev_region(dev_t from,unsigned count)

3. 重新编写驱动

从头来一遍,驱动要多写,形成肌肉记忆,才算掌握!

3.1 创建文件夹
在这里插入图片描述
3.2 把之前的.vscode文件夹拷贝进来
在这里插入图片描述
3.3

//#include <linux/init.h>
//#include <linux/types.h>    //dev_t 
//#include <linux/kernel.h>
//#include <linux/delay.h>
//#include <linux/kdev_t.h>   //设备号头文件
//#include <linux/kern_levels.h> //printk 消息级别
#include <linux/printk.h>   //printk
#include <linux/ide.h>
#include <linux/module.h>


/********************************************************************
 * 文件名:chrdevbase.c
 * 作者:张亚胜
 * 版本:V1.0
 * 描述:chrdevbase驱动文件
 * 其他:无
 * 网站:www.s123.xyz
 * 日志:出版V1.0 2020/7/26 张亚胜创建
 * ******************************************************************/

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

static char readbuf[100];                   /*读缓冲区*/
static char writebuf[100];                  /*写缓冲区*/
static char kerneldata[] = {"kernal data"}; /*测试数据*/



/*
* @description   : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp  : 设备文件,file 结构体有个叫做 private_data 的成员变量
*                   一般在 open 的时候将 private_data 指向设备结构体。
* @return        : 0 成功;其他 失败
*/
static int chrtest_open(struct inode *inode, struct file *filp)
{
    //printk("chrtest_open\r\n");
    return 0;
}

/*
* @description   : 从设备读取数据
* @param – filp  : 设备文件(文件描述符)
* @param - buf   : 返回给用户空间的数据缓冲区
* @param - cnt   : 要读取的数据长度
* @param - offt  : 相对于文件首地址的偏移
* @return        : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retval = 0;
    memset(readbuf,0,sizeof(readbuf));
    memcpy(readbuf,kerneldata,sizeof(kerneldata));
    retval = copy_to_user(buf,readbuf,cnt);
    if(retval == 0){
        printk("kernal read ok\r\n");
    }
    else
    {
        printk("kernal read err!\r\n");
        return retval;
    }
    return cnt;
}

/*
* @description   : 向设备写入数据
* @param - filp  : 设备文件(文件描述符)
* @param - buf   : 要写入设备的数据
* @param - cnt   : 要写入的数据长度
* @param - offt  : 相对于文件首地址的偏移
* @return        : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t chrtest_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retval = 0;
    retval = copy_from_user(writebuf,buf,cnt);
    if(retval == 0){
        printk("write kernal data is %s\r\n",writebuf);
    }
    else{
        printk("write kernal data is err!\r\n");
        return retval;
    }
    return cnt;
}

/*
* @description   : 关闭设备
* @param – inode : 传递给驱动的 inode
* @param - filp  : 设备文件,file 结构体有个叫做 private_data 的成员变量
*                   一般在 open 的时候将 private_data 指向设备结构体。
* @return        : 0 成功;其他 失败
*/
static int chrtest_release(struct inode *inode, struct file *filp)
{
    //printk("chrtest_release\r\n");//屏蔽掉 不然容易和app数据发生踩踏
    return 0;
}

/* 设备操作函数结构体 */
static struct file_operations test_fops = {
    .owner = THIS_MODULE,
    .open = chrtest_open,
    .release = chrtest_release,
    .read = chrtest_read,
    .write = chrtest_write,
};

/*
* @description   : 驱动入口函数
* @param         : 无
* @return        : 0 成功;其他 失败
*/
static int __init chrdevbase_init(void)
{
    int retval = 0;
    retval = register_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME,&test_fops);
    if(retval < 0)
    {
        printk("%s register err!\r\n",CHRDEVBASE_NAME);
        return retval;
    }
    printk("chrdevbase_init\r\n");
    return 0;
}

static void __exit chrdevbase_exit(void)
{
    printk("chrdevbase_exit\r\n");
    unregister_chrdev(200,CHRDEVBASE_NAME);
}

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYS");

4.编写测试app前准备

测试应用程序就是编写linux应用,需要用到c库里面和文件相关的函数。
以下函数可以在ubuntu中通过 “man 2 open” 命令查看详细内容

4.1 open函数

//参数:
//pathname:要打开的设备名
//flags:文件打开模式,O_RDONLY,O_WRONLY,O_RDWR,只读,只写,读写模式,还有很多模式,可以使用逻辑或选择多种
//返回值:打开成功返回文件描述符(非负整数)
int open(const char *pathname,int flags)
----------------------------扩展-----------------------------
O_APPEND 每次写操作都写入文件的末尾
O_CREAT 如果指定文件不存在,则创建这个文件
O_EXCL 如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继I/O 设置为非阻塞
DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
O_RSYNC read 等待所有写入同一区域的写操作完成后再进行。
O_SYNC 等待物理 I/O 结束后再 write,包括更新文件属性的 I/O。

在这里插入图片描述

4.2 read函数

//参数:
//fd:要读取的文件描述符
//buf:读取的数据保存位置
//count:读取数据的长度 字节数
//返回值:读取成功 返回读到的字节数;如果返回0 表示读到了文件末尾;如果返回负数 读取失败
ssize_t read(int fd,void *buf,size_t count)

在这里插入图片描述
4.3 write函数

//参数:
//fd:文件描述符
//buf:将要写入的数据
//count:写入数据的长度
//返回值:写入成功返回写入的字节数;返回0表示没有写入数据;返回负数 写入失败
ssize_t write(int fd,const void *buf,size_t count)

在这里插入图片描述
4.4 close函数

//参数:
//fd:文件描述符
//返回0成功 负值失败
int close(int fd)

在这里插入图片描述

5.编写测试app

先创建文件chrdevbaseApp.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
/********************************************************************
 * 文件名:chrdevbaseApp.c
 * 作者:张亚胜
 * 版本:V1.0
 * 描述:chrdevbaseApp测试文件
 * 其他:使用方法:./chrdevbaseApp /dev/chrdevbase 1 //读取数据
 *              ./chrdevbaseApp /dev/chrdevbase 2 //写入数据
 * 网站:www.s123.xyz
 * 日志:出版V1.0 2020/7/26 张亚胜创建
 * ******************************************************************/

/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
    int fd,retval;
    char *filename;
    char readbuf[100],writebuf[]={"I am test App!"};

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

    filename = argv[1];

    fd = open(filename,O_RDWR);
    if(fd<0){
        printf("open err!\r\n");
        return -1;
    }

    if(atoi(argv[2]) == 1)
    {
        memset(readbuf,0,100);
        if(read(fd,readbuf,11) < 0)
        {
            printf("read data err!\r\n");
        }
        else
        {
            printf("read data:%s\r\n",readbuf);
        }
    }
    else if(atoi(argv[2]) == 2)
    {
        if(write(fd,writebuf,sizeof(writebuf)) < 0){
             printf("write err!\r\n");
        }
        else{
            printf("write ok!\r\n");
        }
    }
    else
    {
        printf("commod err!\r\n");
    }
    
   retval = close(fd);
   return retval;
}

编译出chrdevbaseApp

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp

6.测试

把chrdevbase.ko 和 chrdevbaseApp拷贝到文件系统目录中

sudo cp chrdevbase.ko chrdevbaseApp /home/zys/linux/nfs/rootfs/lib/modules/4.1.15/ -f

开发板挂载驱动

modprobe chrdevbase.ko

创建文件节点

//c代字符设备
//200 主设备号 0 次设备号
mknod /dev/chrdevbase c 200 0

测试读数据

/lib/modules/4.1.15 # ./chrdevbaseApp /dev/chrdevbase 1
kernal read ok
read data:kernal data

测试写


/lib/modules/4.1.15 # ./chrdevbaseApp /dev/chrdevbase 2
write kernal data is I am test App!
write ok!

7.问题

1.测试读取数据的时候,打印的数据有覆盖现象
在这里插入图片描述
在这里插入图片描述
导致的踩踏 和 乱序现象 ,怎么解决呢? 调试好驱动以后,屏蔽掉驱动中的printk。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值