目錄
前言
驅動入門知識
1.APP 打開的文件在內核中如何表示
2.打開字符設備節點時,內核中也有對應的 struct file
編寫 Hello 驅動程序步驟
1.流程介紹
2.驅動代碼:
3.應用層代碼:
4.本驅動程序的 Makefile 內容:
5.上機實驗:
前言
在編譯驅動程序之前要先編譯內核,原因有三點:
- 驅動程序要用到內核文件
- 編譯驅動時用的內核、開發板上運行到內核,要一致
- 更換板子上的內核后,板子上的其他驅動也要更換
編譯內核步驟看我之前寫過的文章,編譯替換內核_設備樹_驅動_IMX6ULL-CSDN博客
驅動入門知識
1.首先我們通常都是在Linux的終端上打開一個可執行文件,然后可執行文件就會執行程序。那么這個可執行文件做了什么呢?
2.可執行文件先是在應用層讀取程序,其中會有很多庫函數,庫函數是屬于內核之中。而內核又會往下調用驅動層程序。最終驅動層控制具體硬件。
- 其實應用程序到庫是比較容易理解的,比如我們剛學習C語言的時候,使用了printf,scanf等等這些函數。而這些函數就在庫中。
- 庫可以和系統內核相連接,具體怎么實現的我也不太清楚。
- 我們寫了一個驅動程序,就需要告訴內核,這個過程叫做注冊。我們注冊了驅動之后,內核里面就會有這個驅動程序的信息,然后上層應用就可以調用。
3.所以我們只需要知道,咱們需要編寫兩個程序,一個是驅動層的,一個是應用層的,最后驅動層需要注冊進入內核,應用層才能夠使用。其他的先不要管。
4.我們在應用層調用read函數,對應驅動層的read函數。write函數和write函數對應。open函數和open函數對應。close函數和release函數對應(這個為什么不一樣我也不清楚)。
5.我們對Linux 應用程序對驅動程序的調用流程有一個簡單了解之后,我得知道整個程序編寫流程應該怎么做。至于流程為什么是這樣的,我們記住即可。因為這些都是人規定的,如果之后學的深了再進行深究也不遲,現在我們主要是入門? ? ?
1.APP 打開的文件在內核中如何表示
APP 打開文件時,可以得到一個整數,這個整數被稱為文件句柄。對于 APP 的每一個文件句柄,在內核里面都有一個“struct file”與之對應。
我們使用 open 打開文件時,傳入的 flags、mode 等參數會被記錄在內核中對應的 struct file 結構體里(f_flags、f_mode):
int open(const char *pathname, int flags, mode_t mode);
去讀寫文件時,文件的當前偏移地址也會保存在 struct file 結構體的 f_pos 成員里。
2.打開字符設備節點時,內核中也有對應的 struct file
注意這個結構體中的結構體:struct file_operations *f_op,這是由驅動程序提供的。
結構體 struct file_operations 的定義如下:
編寫 Hello 驅動程序步驟
主要為一下七個步驟:
- 確定主設備號,也可以讓內核分配
- 定義自己的 file_operations 結構體
- 實現對應的 drv_open/drv read/drv write 等函數,填入 file operations 結構體
- 把 file_operations 結構體告訴內核: register_chrdev
- 誰來注冊驅動程序啊? 得有一個入口函數:安裝驅動程序時,就會去調用這個入口函數
- 有入口函數就應該有出口函數: 卸載驅動程序時,出口函數調用unregister_chrdev
- 其他完善:提供設備信息,自動創建設備節點: class_create,device_create
1.流程介紹
<1>我們首先需要編寫一個file_operations類型的結構體,這個結構體用于管理驅動程序。之后我們將驅動程序注冊進入內核之后,我們在應用層調用這個驅動,那么就可以直接通過這個結構體來操作驅動中的open,write,read等函數。
<2>實現對應的 drv_open/drv_read/drv_write 等函數,填入 file_operations 結構體。這樣我們在應用層調用open,write,read等函數,就是調用這個驅動了。
這個時候有人可能會問了,有這么多個驅動,我怎么知道open對應的是哪一個驅動?很簡單,咱們在寫應用層程序的時候,是不是第一個參數是需要傳入一個設備號。系統根據這個設備號來判斷是調用的哪一個驅動。
<3>把 file_operations 結構體告訴內核: register_chrdev。我們寫了一個驅動,但是內核是不知道的。那么怎么辦呢?我們就去注冊他,內核就明白,有了這個驅動,然后給他分配一個設備號。之后應用層就可以根據這個設備號來調用驅動層了。
<4> 這個時候,有人就有疑問了,誰來注冊這個結構體?于是我們需要一個入口函數來進行注冊,安裝驅動程序時,就會去調用這個入口函數。
<5>有入口函數就應該有出口函數:卸載驅動程序時,出口函數調用unregister_chrdev。
<6>最后需要加入GPL協議。因為Linux是遵頊GPL協議的,所以你如果需要使用Linux其他的驅動層函數,就必須遵頊GPL協議,強制要求開源代碼。根據這個協議,你可以要求所有使用Linux的廠商提供驅動層源代碼,同時別人也可以要求你公開你的驅動層代碼,這個是相互的。不過很多廠商為了規避這個協議,驅動源代碼很簡單,復雜的東西放在應用層。至于還有一個作者名字的添加,隨便寫不寫。? ? ? ? ? ? ? ? ? ? ? ? ? ?
2.驅動代碼:
hello_drv.c
#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>/* 1. 確定主設備號 */
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;#define MIN(a, b) (a < b ? a : b)/* 3. 實現對應的open/read/write等函數,填入file_operations結構體 */
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_to_user(buf, kernel_buf, MIN(1024, size));return MIN(1024, size);
}static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(kernel_buf, buf, MIN(1024, size));return MIN(1024, size);
}static int hello_drv_open (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static int hello_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 2. 定義自己的file_operations結構體 */
static struct file_operations hello_drv = {.owner = THIS_MODULE,.open = hello_drv_open,.read = hello_drv_read,.write = hello_drv_write,.release = hello_drv_close,
};/* 4. 把file_operations結構體告訴內核:注冊驅動程序 */
/* 5. 誰來注冊驅動程序啊?得有一個入口函數:安裝驅動程序時,就會去調用這個入口函數 */
static int __init hello_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(0, "hello", &hello_drv); /* /dev/hello */hello_class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(hello_class);if (IS_ERR(hello_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "hello");return -1;}device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */return 0;
}/* 6. 有入口函數就應該有出口函數:卸載驅動程序時,就會去調用這個出口函數 */
static void __exit hello_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);device_destroy(hello_class, MKDEV(major, 0));class_destroy(hello_class);unregister_chrdev(major, "hello");
}/* 7. 其他完善:提供設備信息,自動創建設備節點 */module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
3.應用層代碼:
hello_drv_test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./hello_drv_test -w abc* ./hello_drv_test -r*/
int main(int argc, char **argv)
{int fd;char buf[1024];int len;/* 1. 判斷參數 */if (argc < 2) {printf("Usage: %s -w <string>\n", argv[0]);printf(" %s -r\n", argv[0]);return -1;}/* 2. 打開文件 */fd = open("/dev/hello", O_RDWR);if (fd == -1){printf("can not open file /dev/hello\n");return -1;}/* 3. 寫文件或讀文件 */if ((0 == strcmp(argv[1], "-w")) && (argc == 3)){len = strlen(argv[2]) + 1;len = len < 1024 ? len : 1024;write(fd, argv[2], len);}else{len = read(fd, buf, 1024); buf[1023] = '\0';printf("APP read : %s\n", buf);}close(fd);return 0;
}
怎么把.c 文件編譯為驅動程序.ko?
這要借助內核的頂層 Makefile,先設置好交叉編譯工具鏈,編譯好你的板子所用的內核,然后修改 Makefile 指定內核源碼路徑,最后即可執行 make 命令編譯驅動程序和測試程序。
4.本驅動程序的 Makefile 內容:
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88all:make -C $(KERN_DIR) M=`pwd` modules$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.cclean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f hello_drv_testobj-m += hello_drv.o
5.上機實驗:
執行 make 命令編譯驅動程序和測試程序
啟動單板后,可以通過 NFS 掛載 Ubuntu 的某個目錄,訪問該目錄中的程序。
打開內核打印:echo "7 4 1 7" > /proc/sys/kernel/printk
關閉內核打印:echo 0 ? ? ? 4 ? ? ? 0 ? ? ?7 ?> /proc/sys/kernel/printk
insmod 就是install module的縮寫(載入模塊)
insmod hello_drv.ko裝載驅動
ls /dev/hello -l // 驅動程序會生成設備節點?驅動程序會生成設備節點
lsmod 確認驅動已經安裝
我們知道驅動已經安裝好了,那么我們需要知道這個驅動的設備號
cat /proc/devices,查看當前已經被使用掉的設備號
驅動名字與我們在驅動層使用register_chrdev()函數的第二個參數有關
./hello_drv_test // 查看測試程序的用法
./hello_drv_test -w zglnb?// 往驅動程序中寫入字符串
./hello_drv_test -r?// 從驅動程序中讀出字符串