linux內核模塊編程(三)----字符驅動設備開發

先給自己打個廣告,本人的微信公衆號正式上線了,搜索:張笑生的地盤,主要關注嵌入式軟件開發,足球等等,希望大家多多關注,有問題可以直接留言給我,一定盡心盡力回答大家的問題
在這裏插入圖片描述

一 前言

在正式開始今天的文章之前,先談談個人學習linux開發中的心得,將自己學習過程中遇到的問題呈現到大家面前,以便給大家一些啓發,讓大家少走一些彎路。

之前我學習linux開發的過程,基本是沿用自己學習單片機開發的路徑來的:準備好開發板,準備好開發環境,準備好芯片手冊pdf,就可以開始開發學習了。我想大多數從單片機開發,嵌入式開發轉到嵌入式linux開發,應該所走的路都跟我相似。

但是這條路好像在嵌入式linux開發的過程中實現起來會比較困難,尤其是對於初學者來說,當然對於一些高手來講,如果按照嵌入式單片機的開發路徑,那麼他完全可以稱爲嵌入式linux開發的全棧工程師了,這種人要麼是公司的技術大拿,要麼就是公司的底層框架設計師了,業務能力水平那是相當厲害了。

但是,我想對初學者來說,我們不適合這樣的路徑,因爲這樣會讓我們的精力很分散,不能精通於某個角度的學習。還是拿我自己舉例,我在開始進入linux內核模塊編程之前,大量的時間花在瞭如何在一個嵌入式開發板平臺上搭建好linux環境,從uboot,busybox,再到linux內核移植,我花了很長很長時間才能搭建好這個環境,期間遇到一些自己明白並且能搞定的,也遇到一些自己不明白但是能搞定的,還有一些自己不明白也沒法搞定的,最害怕的就是最後一種情況,自己不明白又搞不定,完全卡殼在這裏,因爲linux環境還沒跑起來啊,也就無法做linux內核模塊編程了,搞到自己相當鬱悶,也開始懷疑自己的業務能力。後期,沒有辦法,在各大論壇,qq羣潛水、提問題、發文章,最後雖然解決了問題,但是對於自己的linux內核模塊編程來說,真的是事倍功半,或者說因爲是野路子出生,很多基礎知識不紮實。

希望我的這些彎路能給大家一些啓發,在大家開始看接下來的內容之前,先反思一下自己的學習方法是否正確。當然,我給大家的建議是,如果是做linux內核模塊編程的話,先不要把時間花在uboot,如何移植linux搭建linux環境上,也不要在開發板上學習,最簡單方便實惠的方法就是創建好本地ubuntu虛擬機後,直接基於此開始linux內核模塊編程的學習吧。

二 what

先問自己一個問題:什麼是字符驅動設備?或者先拋去字符兩字,什麼是linux系統驅動?

linux系統驅動是用戶訪問底層硬件設備的橋樑,它將用戶訪問的底層硬件進行封裝,使得用戶不必關心底層硬件的操作,用戶層將這些設備完全當做文件來進行讀寫等操作。

針對這些各種各樣的設備驅動,linux系統將它們分爲了三類:字符設備,塊設備,網絡設備(後兩個在本篇中將不做介紹),如下圖
在這裏插入圖片描述
字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據需要按照先後數據。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制檯和LED設備等。

三 why

  1. 爲什麼需要字符驅動設備程序?我自己寫一個驅動不就好了嗎?

我想這個問題也是做嵌入式單片機開發的人,最容易會問的問題,因爲從他們的工作經驗來看,一個跑在設備上的驅動完全是可以由他一個人來進行的,很多時候也沒有區分用戶空間和內核空間,當然很多人如果有一個很好的框架,他是對app程序和驅動程序有一個比較明顯的區分的。

越是龐大的系統,越涉及多層次的協作,如果上層應用層開發人員完全沒有底層硬件驅動的概念,如果沒有這些設備驅動程序,他們將完全不懂如何開發。

在linux系統下,有一句很經典的話,就是"一切皆文件",上層業務開發人員可以把所有需要訪問的硬件資源,當成文件一樣去操作,那麼對於文件的操作一般都有:打開文件、關閉文件、讀文件、寫文件,所以linux驅動開發需要對這些硬件資源實現這些操作:打開操作、關閉操作、讀操作、寫操作,linux系統會幫我們封裝好這些調用流程,所以這裏不得不說linux系統的強大,以字符驅動設備爲例,調用流程大致如下所示:
在這裏插入圖片描述

四 how

在正式實現一個字符驅動設備編程之前,先給大家普及一個知識,因爲linux內核版本的不斷更新,很多接口API的實現以及名字都在不停變化,這樣造成的一個結果是,如果你完全不知道自己的內核版本,也不知道我開發時所用的內核版本,直接將我下面的源代碼拷貝回去編譯的話,會有一大堆報錯,相信新手此時也會一頭霧水,所以給大家如下建議

1. 先確認自己Ubuntu當前的linux內核版本,在console下輸入命令 uname -r,如下面的截圖
2. linux內核官網:https://elixir.bootlin.com/linux/v4.15/source
3. 如果你的linux內核版本和我不一致,當你直接拷貝我下面的函數到你的環境下編譯出錯時,不要慌張,一般都是我調用的API在你的linux內核下面可能沒有或者名字不對了,到上面的官網上根據這些關鍵字搜索即可
4. 再次重申,下面的示例程序,linux內核版本是4.15,請大家一定注意內核版本的差異

在這裏插入圖片描述
再次強調,請大家一定注意內核版本的差異,我下面的示例程序是在4.15的linux內核版本下開發的。
根據之前的文章《linux內核模塊編程》,我們知道linux內核編程的一般框架是init和exit

#include <linux/module.h>
#include <linux/kernel.h>

int test_init(void)
{
    ......
    return 0;
}

void test_exit(void)
{
    ......
}

MODULE_LICENSE("GPL");
module_init(test_init);
module_exit(test_exit);

但是因爲我們今天實現的字符驅動設備開發,除了init和exit之外,我們還需要實現open,read,write以及close,今天先實現open,read和close,源代碼如下

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>

static int major;
static struct class *chardev_class;
static struct device *chardrv_class_dev;

static int chardev_drv_open(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "chardev_drv_open\n");
	return 0;
}

static ssize_t chardev_drv_read (struct file *filp, char __user *buf,
	size_t size, loff_t *ppos)
{
	printk(KERN_INFO "chardev_drv_read\n");
	return 0;
}

int chardev_drv_close(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "chardev_drv_close\n");
	return 0;
}

static struct file_operations chardev_drv_fops = {
	.owner   = THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
	.open    = chardev_drv_open,
	.read    = chardev_drv_read,
	.release = chardev_drv_close,
};

static int chardev_drv_init(void)
{
	printk(KERN_INFO "already chardev_drv_init\n");

	major = register_chrdev(0, "chardev_drv", &chardev_drv_fops);
	chardev_class = class_create(THIS_MODULE, "chardrv");
	chardrv_class_dev = device_create(chardev_class, NULL, MKDEV(major, 0), NULL, "chardev_drv"); /* /dev/xyz */

	return 0;
}

static void chardev_drv_exit(void)
{
	printk(KERN_INFO "new exit chardev_drv\n");
	unregister_chrdev(major, "chardev_drv");
	device_unregister(chardrv_class_dev);
	class_destroy(chardev_class);
}

module_init(chardev_drv_init);
module_exit(chardev_drv_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m:=char_dev.o

KDIR:= /lib/modules/$(shell uname -r)/build
PWD:= $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
	

五 test

  1. 編譯make
  2. 加載驅動sudo insmod char_dev.ko
  3. 查看打印demsg | tail,發現內核加載成功
    在這裏插入圖片描述
  4. 因爲我們創建了一個字符驅動設備,這個字符驅動設備在/dev路勁下,開頭的"c"表示字符設備
    在這裏插入圖片描述
  5. 卸載`sudo rmmod char_dev
    在這裏插入圖片描述
  6. 再次查看/dev, ls -al | grep timer,發現已經沒有這個字符設備了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章