第一个Linux驱动
1. 建立工程
2. 建立.vscode
c_pp_properties.json
{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/szm/linux/IMX6ULL/szm_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/include", "/home/szm/linux/IMX6ULL/szm_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include", "/home/szm/linux/IMX6ULL/szm_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include/generated/"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "c11","cppStandard": "c++17","intelliSenseMode": "gcc-x64"}],"version": 4settings.json
{"search.exclude": {"**/node_modules": true,"**/bower_components": true,"**/*.o":true,"**/*.su":true, "**/*.cmd":true,"Documentation":true, },"files.exclude": {"**/.git": true,"**/.svn": true,"**/.hg": true,"**/CVS": true,"**/.DS_Store": true, "**/*.o":true,"**/*.su":true, "**/*.cmd":true,"Documentation":true, } }3.Makefile
# KERNELDIR表示內核源碼路徑 KERNELDIR := /home/szm/linux/IMX6ULL/szm_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga # 表示當前路徑,即pwd CURRENT_PATH := $(shell pwd)# 將chadevbase.c編譯成模塊 obj-m := chadevbase.obuild : kernel_modules# 具體的編譯命令,后面的 modules 表示編譯模塊,-C 表示將當前的工作目錄切 # 換到指定目錄中,也就是 KERNERLDIR 目錄。M 表示模塊源碼目錄,“make modules”命令 # 中加入 M=dir 以后程序會自動到指定的 dir 目錄中讀取模塊的源碼并將其編譯為.ko 文件。 kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean4.代碼
4.1 驅動模塊加載與卸載
模塊編譯完成之后擴展名為ko,有兩種命令可以加載模塊:insmod和modprobe。insmod是最簡單的模塊加載命令,此命令用于加載指定的.ko 模塊。
insmod 命令不能解決模塊的依賴關系,比如 drv.ko 依賴 first.ko 這個模
塊,就必須先使用insmod 命令加載 first.ko 這個模塊,然后再加載 drv.ko這個模塊。但是 modprobe 就不會存在這個問題,modprobe 會分析模塊的依賴關系,然后會將所有的依賴模塊都加載到內核中,因此modprobe 命令相比insmod 要智能一些。modprobe 命令主要智能在提供了模塊的依賴性分析、錯誤檢查、錯誤報告等功能,推薦使用 modprobe 命令來加載驅動。modprobe 命令默認會去/lib/modules/kernel-version目錄中查找模塊,比如本書使用的 Linux kernel 的版本號為 4.1.15,因此 modprobe 命令默認會到/lib/modules/4.1.15 這個目錄中查找相應的驅動模塊,一般自己制作的根文件系統中是不會有這個目錄的,所以需要自己手動創建。
我們把編譯好的ko文件放到nfs中
在加載模塊驅動之前要先加demod
然后使用加載命令,加載成功后可以用lsmod查看驅動
使用rmmod卸載驅動
4.2 字符設備的注冊與注銷
- 我們需要向系統注冊一個字符設備,使用函數register_chrdev
- 卸載驅動的時候,需要注銷掉,前面注冊的字符設備unregister_chrdev
4.3 設備號
- linux內核使用dev_t
typedef __ kernel_dev_t dev_t
typedef __ u32 __ kernel_dev_t
typedef unsigned int __ u32 - Linux內核將設備號分成兩部分,主設備號和次設備號。主設備號占用前12位,次設備號占用后20位
- 設備號的操作函數
從dev_t獲取主設備號和次設備號,MAJOR(dev_t), MINOR(dev_t)。也可以使用主設備號和次設備號,通過mkdev(major,minor), 所以一定保證驅動的主設備號和次設備號一定是唯一的,不能沖突
查看當前主設備號的使用情況:
cat /proc/devices
我們看到200沒有用
4.4 file_opterations開發,驅動框架
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h>#define CHRDEVBASE_MAJOR 200 /* 主設備號 */ #define CHRDEVBASE_NAME "chrdevbase" /* 名字 */static int chrdevbase_open(struct inode *inode, struct file *filp) {printk("chrdevbase_open\r\n");return 0; }/* 對應的驅動關閉注意和原來的區別 */ static int chrdevbase_release(struct inode *inode, struct file *filp) {printk("chrdevbase_release\r\n");return 0; }static ssize_t chrdevbase_read(struct file *filp, __user char *buf, size_t count, loff_t *ppos) {printk("chrdevbase_read\r\n");return 0; }static ssize_t chrdevbase_write(struct file *filp, __user const char *buf, size_t count, loff_t *ppos) {printk("chrdevbase_write\r\n");return 0; }static struct file_operations chrdevbase_fops={.owner = THIS_MODULE,.open = chrdevbase_open,.release = chrdevbase_release,.read = chrdevbase_read,.write = chrdevbase_write, };static int __init chrdevbase_init(void) { int ret = 0;printk("module init\r\n"); //linux內核的打印函數/* 注冊字符設備 *//* file_opterations就是要實現的 */ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if(ret < 0){printk("chrdevbase init failed\r\n");}return 0; } static void __exit chrdevbase_exit(void) {printk("module exit\r\n");/* 注銷字符設備 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);return ; }/* 模塊入口與出口 */ module_init(chrdevbase_init); /* 入口 */ /* 當裝載的時候自動執行這個函數,卸載執行下面的函數 */ module_exit(chrdevbase_exit); /* 出口 */ /* 括號里填寫驅動入口函數 */MODULE_LICENSE("GPL"); MODULE_AUTHOR("Shao Zheming");4.5 應用程序編寫
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h>/* * argc: 應用程序參數個數* argv[]: 參數是什么,具體的參數,說明參數是字符串的形式* .chrdevbaseApp <filename> 調用情況* */ int main(int argc, char *argv[]) {int ret = 0;int fd = 0; //文件描述符char *filename;char readBuf[100], writeBuf[100];// 當輸入這個命令時,.chrdevbaseApp <filename> // 第一個參數,也就是argv[0]就是.chrdevbaseApp// 第二個參數,也就是argv[1]就是<filename>filename = argv[1];// O_RDWR可讀可寫fd = open(filename, O_RDWR);if(fd < 0){printf("Can't open file %s \r\n", filename); //應用測試就要用printf了return -1;}/* 讀和寫測試 *//* read */// 50指的是從驅動里面讀多少個字節// 返回的是你讀的字節數, -1是錯了,如果小于50,說明驅動只能給你小于50,沒錯ret = read(fd, readBuf, 50);if(ret < 0){printf("read file %s failed \r\n", filename);}else{}/* write */ret = write(fd, writeBuf, 50);if(ret < 0){printf("write file %s failed \r\n", filename);}else{}/* write */ret = close(fd);if(ret < 0){printf("close file %s failed \r\n", filename);}return 0; }注意是arm架構,所以在Ubuntu上運行肯定報錯
5. 測試
進入dev查看chrdevbase,發現沒有這個東西,需要手動創建
c是字符設備的意思
6. 完善這個驅動
要求可以讀寫程序
chrdevbase.c
測試APP文件
chrdevbaseApp.c
總結
以上是生活随笔為你收集整理的第一个Linux驱动的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Decorating The Pastu
- 下一篇: Antv/g6 - 鼠标事件