1、字符設備注冊與注銷的函數原型”
/*字符設備注冊的函數原型*/
static inline int register_chrdev(unsigned int major,\
????????????????????????????????? const char *name,? \
???????????????????????????????? ?const struct file_operations *fops)
/*
major:主設備號,Limnux下每個設備都有一個設備號,設備號分為主設備號和次設備號兩部分。
name:設備名字,指向一串字符串。
fops:結構體file_operations類型指針,指向設備的操作函數集合變量。
*/
/*字符設備注銷的函數原型*/
static inline void unregister_chrdev(unsigned int major,\
?????????????????????????? ??????????const char *name)
/*
major:主設備號,Limnux下每個設備都有一個設備號,設備號分為主設備號和次設備號兩部分。
name:設備名字,指向一串字符串。
*/
2、Linux設備號
1)、使用設備號的原因:為了方便管理,Linux 中每個設備都有一個設備號。
2)、設備號的組成
Linux設備號是由主設備號和次設備號兩部分組成;
主設備號表示某一個具體的驅動;
次設備號表示使用這個驅動的各個設備。
Linux使用“dev_t的數據類型”表示設備號;
“dev_t的數據類型”定義在文件“include/linux/types.h”里面,定義如下:
typedef __u32 __kernel_dev_t;
//為“__u32”起個別名叫“__kernel_dev_t”
typedef __kernel_dev_t dev_t;
//為“__kernel_dev_t”起個別名叫“dev_t”
由此可見,dev_t是 umsigned int類型,是一個32位的數據類型。這個32位的數據就是Linux設備號,它是由“主設備號”和“次設備號”兩部分構成,其中高12位為“主設備號”,低20位為“次設備號”。因此,Linux系統中主設備號范圍為0~4095,所以,大家在選擇主設備號的時候一定不要超過這個范圍。
在文件“include/linux/kdev_t.h”中提供了幾個關于設備號的操作函數(本質是宏),如下所示:
#define MINORBITS 20???????? //定義“次設備號”占據低20位
#define MINORMASK ((1U << MINORBITS) - 1) //定義“次設備號”的掩碼值
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
//輸入參數dev為“Linux設備號”
//將dev右移20位得到“主設備號”
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
//輸入參數dev為“Linux設備號”
//將dev與0xFFFFF相與后得到“次設備號”
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
//輸入參數ma為“主設備號”
//輸入參數mi為“次設備號”
//將ma左移20位,再與mi相或,就得到“Linux設備號”
設備號的申請函數
int alloc_chrdev_region(dev_t *dev,\
??????????????????????? unsigned baseminor,\
??????????????????????? unsigned count,\
??????????????????????? const char *name)
//dev:保存申請到的設備號
//baseminor:次設備號的起始地址
//alloc_chrdev_region可以申請一段連續的多個“設備號”,這些“設備號”的“主設備號”是一樣的,但是“次設備號”不同。“次設備號”以baseminor為起始值開始遞增。通常baseminor的值為0。
//count:要申請的設備號數量;
// name:表示“設備名字”
設備號的釋放函數
void unregister_chrdev_region(dev_t from, unsigned count)
// from:要釋放的設備號;
// count:表示從from開始,要釋放的設備號數量。
3、字符設備加載,注冊,注銷和卸載的一般模板
#define xxx_MAJOR?? 200
//定義主設備號
//靜態分配設備號:在串口輸入“cat/proc/devices”查詢當前已用的主設備號
//然后使用一個“沒有被使用的設備號”作為該設備的的主設備號
#define xxx_NAME?? "xxxName"? //定義設備的名字。
const struct file_operations xxx_fops = {};
//聲明一個file_operations結構變量
/*驅動入口函數 */
static int? __init xxx_init(void)
{
??? int ret;
??? ret = register_chrdev(xxx_MAJOR, xxx_NAME, &xxx_fops);
//注冊字符設備驅動
//xxx_MAJOR為主設備號,采用宏xxx_NAME定義設備名字
//xxx_fops是設備的操作函數集合,它是file_operations結構變量
??? if (ret < 0) {
?????? pr_err("xxx_init is failed!!!\r\n");
?????? return ret;
??? }
??? else pr_err("xxx_init is ok!!!\r\n");
?? return 0;
}
/*驅動出口函數 */
static void __exit xxx_exit(void)
{
?? /*出口函數具體內容 */
??? unregister_chrdev(xxx_MAJOR, xxx_NAME);
????//注銷字符設備驅動
//xxx_MAJOR為主設備號,采用宏xxx_NAME定義設備名字
}
module_init(xxx_init); //聲明xxx_init()為驅動入口函數
module_exit(xxx_exit); //聲明xxx_exit()為驅動出口函數
4、查看“linux-5.4.31”中的設備注冊和注銷
打開虛擬機上“VSCode”,點擊“文件”,點擊“打開文件夾”,點擊“zgq”,點擊“linux”,點擊“atk-mp1”,點擊“linux”,點擊“my_linux”,點擊“linux-5.4.31”,見下圖:
點擊“確定”
點擊“查看”,點擊“搜索”,輸入“register_chrdev”
假如我們點擊的是“rtlx-cmp.c”,見下圖:
“rtlx-cmp.c”程序如下:
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <asm/mips_mt.h>
#include <asm/vpe.h>
#include <asm/rtlx.h>
static int major;//major:設備號
/*從字面意思看,是一個中斷*/
static void rtlx_interrupt(void)
{
??? int i;
??? struct rtlx_info *info;
??? struct rtlx_info **p = vpe_get_shared(aprp_cpu_index());
??? if (p == NULL || *p == NULL)
?????? return;
??? info = *p;
??? if (info->ap_int_pending == 1 && smp_processor_id() == 0) {
?????? for (i = 0; i < RTLX_CHANNELS; i++) {
?????????? wake_up(&channel_wqs[i].lx_queue);
?????????? wake_up(&channel_wqs[i].rt_queue);
?????? }
?????? info->ap_int_pending = 0;
??? }
}
/*從字面看是講中斷堆棧指針*/
void _interrupt_sp(void)
{
??? smp_send_reschedule(aprp_cpu_index());
}
/*入口函數初始化*/
int __init rtlx_module_init(void)
{
??? struct device *dev;
??? int i, err;
??? if (!cpu_has_mipsmt) {
?????? pr_warn("VPE loader: not a MIPS MT capable processor\n");
?????? return -ENODEV;
??? }
??? if (num_possible_cpus() - aprp_cpu_index() < 1) {
?????? pr_warn("No TCs reserved for AP/SP, not initializing RTLX.\n"
?????????? "Pass maxcpus=<n> argument as kernel argument\n");
?????? return -ENODEV;
??? }
??? major = register_chrdev(0, RTLX_MODULE_NAME, &rtlx_fops);
//注冊字符設備驅動
???//0為主設備號,采用宏RTLX_MODULE_NAME定義設備名字
???//rtlx_fops是設備的操作函數集合,它是file_operations結構變量
??? if (major < 0) {//讀取主設備號小于0,則打印錯誤信息
?????? pr_err("rtlx_module_init: unable to register device\n");
?????? return major;
??? }
??? /* initialise the wait queues */
??? for (i = 0; i < RTLX_CHANNELS; i++) {
?????? init_waitqueue_head(&channel_wqs[i].rt_queue);
???????//初始化等待隊列頭
?????? init_waitqueue_head(&channel_wqs[i].lx_queue);
???????//初始化等待隊列頭
?????? atomic_set(&channel_wqs[i].in_open, 0);//狀態重置
?????? mutex_init(&channel_wqs[i].mutex);//初始化互斥體
?????? dev = device_create(mt_class, NULL, MKDEV(major, i), NULL,
????????????? ??? "%s%d", RTLX_MODULE_NAME, i);
//創建設備, major為主設備號,i為次設備號
//參數mt_class表示該設備位于mt_class類下面
// parent為NULL表示沒有父設備
//將major左移20位,再與i相或,就得到“Linux設備號”
//drvdata為NULL表示沒有使用設備文件
//采用RTLX_MODULE_NAME宏定義指向字符串表示設備名
?????? if (IS_ERR(dev)) {
?????????? while (i--)
????????????? device_destroy(mt_class, MKDEV(major, i));
??????????????//刪除創建的設備
??????????????//參數mt_class是設備所處的類,參數i是設備號
?????????? err = PTR_ERR(dev);
?????????? goto out_chrdev;
?????? }
??? }
??? /* set up notifiers */
??? rtlx_notify.start = rtlx_starting;
??? rtlx_notify.stop = rtlx_stopping;
??? vpe_notify(aprp_cpu_index(), &rtlx_notify);
??? if (cpu_has_vint) {
?????? aprp_hook = rtlx_interrupt;
??? } else {
?????? pr_err("APRP RTLX init on non-vectored-interrupt processor\n");
?????? err = -ENODEV;
?????? goto out_class;
??? }
??? return 0;
out_class:
??? for (i = 0; i < RTLX_CHANNELS; i++)
?????? device_destroy(mt_class, MKDEV(major, i));
???????//刪除創建的設備
???????//參數mt_class是要刪除的設備所處的類,參數i是要刪除的設備號
out_chrdev:
??? unregister_chrdev(major, RTLX_MODULE_NAME);
???//注銷字符設備驅動
???//major為主設備號,采用宏RTLX_MODULE_NAME定義設備
??? return err;
}
/*出口函數初始化*/
void __exit rtlx_module_exit(void)
{
??? int i;
??? for (i = 0; i < RTLX_CHANNELS; i++)
?????? device_destroy(mt_class, MKDEV(major, i));
?????//刪除創建的設備
?????//參數mt_class是要刪除的設備所處的類,參數i是要刪除的設備號
??? unregister_chrdev(major, RTLX_MODULE_NAME);
???//注銷字符設備驅動
????//major為主設備號,采用宏RTLX_MODULE_NAME定義設備名字
??? aprp_hook = NULL;
}
5、編寫“字符設備驅”動注冊與注銷的程序
1)、創建“/home/zgq/linux/Linux_Drivers/01_MyCharDevice/”目錄
輸入“cd /home/zgq/linux/Linux_Drivers/”
切換到“/home/zgq/linux/Linux_Drivers/”目錄
輸入“ls”,查詢“/home/zgq/linux/Linux_Drivers/”目錄下的文件和文件夾
輸入“mkdir 01_MyCharDevice”
創建“/home/zgq/linux/Linux_Drivers/01_MyCharDevice/”目錄
輸入“ls”,查詢“/home/zgq/linux/Linux_Drivers/”目錄下的文件和文件夾
輸入“cd 01_MyCharDevice/”
切換到“/home/zgq/linux/Linux_Drivers/01_MyCharDevice/”目錄
輸入“pwd”獲取絕對路徑
輸入“cd /home/zgq/linux/Linux_Drivers/00_My_TestDriver/”
切換到“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目錄
輸入“ls”,查詢“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目錄下的文件和文件夾
輸入“cp Makefile /home/zgq/linux/Linux_Drivers/01_MyCharDevice回車”
拷貝“Makefile”
2)、修改Makefile文件
Makefile文件內容如下:
KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31
#使用“:=”將其后面的字符串賦值給KERNELDIR
CURRENT_PATH := $(shell pwd)
#采用“shell pwd”獲取當前打開的路徑
#使用“$(變量名)”引用“變量的值”
obj-m := MyCharDevice.o
#生成“obj-m”需要依賴“MyCharDevice.o”
build: kernel_modules
#生成“build”需要依賴“kernel_modules”
??????? @echo $(KERNELDIR)
#輸出KERNELDIR的值為“/home/zgq/linux/atk-mp1/linux/linux-5.4.31”
??????? @echo $(CURRENT_PATH)
#輸出CURRENT_PATH的值為/home/zgq/linux/Linux_Drivers/00_My_TestDriver”
??????? @echo $(MAKE)
#輸出MAKE的值為make
kernel_modules:
??????? $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
#后面的"modules"表示編譯成模塊
#“KERNELDIR”上面定義為“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目錄”
#“CURRENT_PATH”上面定義為“當前的工作目錄”
#“-C $(KERNELDIR) M=$(CURRENT_PATH) ”表示將“當前的工作目錄”切換到“指定的目錄”中
#即切換到“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”。
#M表示模塊源碼目錄
#在“make和modules”之間加入“M=$(CURRENT_PATH)”,表示切換到由“CURRENT_PATH”指定的目錄中讀取源碼,同時將其編>譯為.ko 文件
clean:
??????? $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
#“KERNELDIR”上面定義為“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目錄”
#“CURRENT_PATH”上面定義為“當前的工作目錄”
3)、使用VSCode創建“MyCharDevice.c”文件
添加內容如下:
#include <linux/init.h>?????? //必須要包含的頭文件
#include <linux/module.h>???? //必須要包含的頭文件
#include <linux/string.h>???? //下面要用到字符串,顯然也要包含
#include <linux/kernel.h>???? //必須要包含的頭文件
#include <linux/device.h>??? ?//必須要包含的頭文件
#include <linux/fs.h>???????? //使能結構體"file_operations"
#define MyCharDevice_MAJOR 200
//定義主設備號
//可以通過串口輸入“cat /proc/devices”查詢當前已用的主設備號
#define MyCharDevice_NAME "MyCharDeviceName"
//使用MyCharDevice_NAME指向一串字符串表示設備的名字。
const struct file_operations MyCharDevice_fops = {};
//聲明file_operations結構變量MyCharDevice_fops
//它是指向設備的操作函數集合變量
/*入口函數初始化*/
static int __init MyCharDevice_init(void)
{
??? int ret = 0;
??? printk("MyCharDevice_init\r\n");
??? ret = register_chrdev(MyCharDevice_MAJOR, MyCharDevice_NAME, &MyCharDevice_fops);
??? //返回的ret=0表示字符設備驅動注冊成功
??? //主設備號為MyCharDevice_MAJOR
??? //設備名字為RTLX_MODULE_NAME宏定義
??? //MyCharDevice_fops是設備的操作函數集合
??? if (ret < 0) {
?????? pr_err("MyCharDevice_init is failed!!!\r\n");
?????? return ret;
??? }
??? else printk("ret=%d\r\n",ret);
??? return ret;
}
/*出口函數初始化*/
static void __exit MyCharDevice_exit(void)
{
??? printk("MyCharDevice_exit\r\n");
??? unregister_chrdev(MyCharDevice_MAJOR, MyCharDevice_NAME);
??? //主設備號為MyCharDevice_MAJOR的值
??? //設備名字為MyCharDevice_NAME宏定義的字符串“MyCharDeviceName”
}
module_init(MyCharDevice_init);
/*將MyCharDevice_init()指定為入口函數*/
module_exit(MyCharDevice_exit);
/*將MyCharDevice_exit()指定為出口函數*/
MODULE_AUTHOR("Zhanggong");//添加作者名字
MODULE_DESCRIPTION("This is test module!");//模塊介紹
MODULE_LICENSE("GPL");//LICENSE采用“GPL協議”
MODULE_INFO(intree,"Y");
//去除顯示“loading out-of-tree module taints kernel.”
4)、編譯
輸入“cd /home/zgq/linux/Linux_Drivers/01_MyCharDevice/”
輸入“ls”
輸入“sudo cp? MyCharDevice.ko? /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31”
輸入密碼“123456回車”
輸入“cd /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31”
輸入“ls”
5)、測試
啟動開發板,從網絡下載程序
輸入“root”
輸入“cd /lib/modules/5.4.31/”
在nfs掛載中,切換到“/lib/modules/5.4.31/”目錄,
注意:“lib/modules/5.4.31/”在虛擬機中是位于“/home/zgq/linux/nfs/rootfs/”目錄下,但在開發板中,卻是位于根目錄中。
輸入“ls”
輸入“depmod”,驅動在第一次執行時,需要運行“depmod”
輸入“modprobe MyCharDevice.ko”,加載“MyCharDevice.ko”模塊
輸入“lsmod”查看有哪些驅動在工作
輸入“rmmod MyCharDevice.ko”,卸載“MyCharDevice.ko”模塊
注意:輸入“rmmod MyCharDevice”也可以卸載“MyCharDevice.ko”模塊
輸入“lsmod”查看有哪些驅動在工作
輸入“modprobe MyCharDevice.ko”,加載“MyCharDevice.ko”模塊
輸入“cat /proc/devices回車”查詢設備號