Linux驱动开发学习笔记【1】:字符设备驱动开发

目录

一、字符设备驱动

二、字符设备驱动开发步骤

1、字符设备驱动模块编译

2、驱动模块的加载与卸载

3、字符设备注册与注销具体实现

4、实现设备的具体操作函数

三、测试APP编写与测试


一、字符设备驱动

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

应用程序运行在用户空间,而Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。open、close、write 和read 等这些函数是由C 库提供的,在Linux 系统中,系统调用作为C 库的一部分。当我们调用open 函数的时候流程如图所示

扫描二维码关注公众号,回复: 12060754 查看本文章

二、字符设备驱动开发步骤

编写驱动的时候注意事项:

1、编译驱动的时候需要用到linux内核源码!因此要解压缩linux内核源码,编译linux内核源码。得到zImage和.dtb。需要使用编译后的到的zImage和dtb启动系统。

2、从SD卡启动,SD卡烧写了uboot。uboot通过tftp从ubuntu里面获取zimage和dtb,rootfs也是通过nfs挂载。

3、设置bootcmd和bootargs

setenv bootargs 'console=ttymxc0,115200
  root=/dev/nfs rw
  nfsroot=192.168.199.158:/home/denghengli/linux/nfs/rootfs
  ip=192.168.199.20:192.168.199.158:192.168.199.1:255.255.255.0::eth0:off'
setenv bootcmd ‘tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;’

4、将编译出来的.ko文件放到根文件系统里面 /lib/modules/<kernel-version> 目录中。

1、字符设备驱动模块编译

第一步:创建VSCode工程

第二步:为VSCode添加头文件路径

源码中的头文件路径。打开 VSCode,按下 Crtl+Shift+P”打开 VSCode的控制台,然后输入 C/C++: Edit configurations(JSON) ”,打开 C/C++编辑配置文件,打开以后会自动在 .vscode目录下生成一个名为 c_cpp_properties.json的文件,includePath 表示头文件路径,需要将 Linux源码里面的头文件路径添加进来

"${workspaceFolder}/**", 
"/home/denghengli/linux/IMX6ULL/linux/nxptoalpha_linux/include", 
"/home/denghengli/linux/IMX6ULL/linux/nxptoalpha_linux/arch/arm/include", 
"/home/denghengli/linux/IMX6ULL/linux/nxptoalpha_linux/arch/arm/include/generated/"

第三步:新建chardevbase.c字符驱动文件,编写Makefile

KERNELDIR := /home/denghengli/linux/IMX6ULL/linux/nxptoalpha_linux

CURRENT_PATH := $(shell pwd)
#obj-m表示将 chardevbase.c这个文件编译为chardevbase.ko模块。
obj-m := chardevbase.o

build: kernel_modules

#具体的编译命令,后面的 modules表示编译模块,-C表示将当前的工作目录切
#换到指定目录中,也就是 KERNERLDIR目录。 M表示模块源码目录,“ make modules”命令
#中加入 M=dir以后程序会自动到指定的 dir目录中读取模块的源码并将其编译为 .ko文件。
kernel_modules :
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

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

完成之后,输入 make 就会编译出 chardevbase.ko 的动态模块文件

2、驱动模块的加载与卸载

在内核启动起来之后,进入根文件系统,使用insmod或modprobe加载驱动,insmod命令不能解决模块的依赖关系,modprobe会分析模块的依赖关系,然后将所有模块的依赖模块都加载到内核中,因此modprobe命令相比insmod要智能一些;modprobe命令默认会去根文件系统下的/lib/modules/<kernel-version>目录中查找模块。对于一个新的模块使用modprobe加载的时候需要先调用一下depmod命令。移除驱动使用命令rmmod。

比如加载chardevbase.ko 
insmod chardevbase.ko 

depmod modprobe chardevbase.ko 
rmmod chardevbase

驱动模块加载成功以后可以使用lsmod查看一下。

lsmode

卸载模块使用rmmod命令

3、字符设备注册与注销具体实现

驱动模块加载和卸载 实际上 就是在向系统注册和注销这个字符设备。

1、我们需要在加载驱动模块函数chardevbase_init 中使用函数register_chrdev向系统注册一个字符设备,卸载驱动的时候需要注销掉前面注册的字符设备,使用函数unregister_chrdevchardevbase_exit中注销字符设备

2、Linux内核将设备号分为两部分:主设备号和次设备号。主设备号占用前12位,次设备号占用低20位。设备号的操作函数或宏:

从dev_t获取主设备号和次设备号:MAJOR(dev_t),MINOR(dev_t)
使用主设备号和次设备号构成dev_t:MKDEV(major,minor)

如果使用静态分配设备号,在设置时需要查看下当前系统都用了哪些设备号,避免冲突,使用命令: 

cat /proc/devices

3、添加LICENSE和作者信息

/*
    模块输入与输出
 */
static int __init chardevbase_init(void)
{
    int ret = 0;
    printk("chardevbase_init\r\n");

    /*注册字符设备*/
    ret = register_chrdev(CHARDEVBASE_MAJOR, CHARDEVBASE_NAME, &chardevbase_fops);
    if (ret < 0){
        printk("chardevbase init failed!\r\n");
    }
    return 0;
}

static void __exit chardevbase_exit(void)
{
    printk("chardevbase_exit\r\n");

    /*卸载字符设备*/
    unregister_chrdev(CHARDEVBASE_MAJOR, CHARDEVBASE_NAME);
}

module_init(chardevbase_init); /*入口*/
module_exit(chardevbase_exit); /*出口*/

MODULE_AUTHOR("denghengli");
MODULE_LICENSE("GPL");

4、实现设备的具体操作函数

在向系统注册完字符设备后,需要自行添加设备相关的 file_operations

/*
 字符设备操作集合
*/
static struct file_operations chardevbase_fops = {
    .owner = THIS_MODULE,
    .open = chardevbase_open,
    .release = chardevbase_release,
    .read = chardevbase_read,
    .write = chardevbase_write,
};

三、测试APP编写与测试

编写驱动程序的时候,可以使用Linux自带的 man 手册查看函数的使用方法。使用方法为 man [section] [command],例如 man 1 ls

1、Standard commands (标准命令)
2、System calls (系统调用)
3、Library functions (库函数)
4、Special devices (设备说明)
5、File formats (文件格式)
6、Games and toys (游戏和娱乐)
7、Miscellaneous (杂项)
8、Administrative Commands (管理员命令)
9 其他(Linux特定的), 用来存放内核例

1、编写 chardevbaseAPP.c 应用测试程序,编译成chardevbaseAPP执行文件,将其拷贝至 rootfs/lib/modules/4.1.15/

arm-linux-gnueabihf-gcc chardevbaseAPP.c -o chardevbaseAPP 
sudo cp chardevbaseAPP /home/denghengli/linux/nfs/rootfs/lib/modules/4.1.15/ -f

2、加载驱动模块,查看chardevbase驱动设备的设备号

modprobe chardevbase.ko 
cat /proc/devices

3、创建设备节点文件

驱动加载成功需要在 /dev目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。加载完驱动模块后,会自动在 /dev创建的,但是这里还没有注册,所以先手动注册一个。如果 chardevbaseAPP想要读写 chardevbase设备,直接对 /dev/chardevbase进行读写操作即可。相当于 /dev/chardevbase这个文件是 chardevbase设备在用户空间中的实现。

/*其中“ mknod”是创建节点命令 ,“/dev/chardevbase”是要创建的节点文件 
*c表示这是个字符设备,200是设备的主设备号 0”是设备的次设备号。
*/

mknod /dev/chardevbase c 200 0

4、执行测试程序

./chardevbaseAPP /dev/chardevbase

猜你喜欢

转载自blog.csdn.net/m0_37845735/article/details/106892621