網(wǎng)站開發(fā)使用哪種語言免費網(wǎng)站站長查詢
文章目錄
- 注冊雜項設備
- 驅(qū)動模塊傳參
- 注冊字符設備
開發(fā)環(huán)境: windows + ubuntu18.04 + 迅為rk3568開發(fā)板
注冊雜項設備
相較于字符設備,雜項設備有以下兩個優(yōu)點:
- 節(jié)省主設備號:雜項設備的主設備號固定為 10,在系統(tǒng)中注冊多個 misc 設備驅(qū)動時,只需使用子設備號進行區(qū)分即可。
- 使用簡單:相比如普通的字符設備驅(qū)動, misc驅(qū)動只需要將基本信息通過結構體傳遞給相應處理函數(shù)即可。
在linxu系統(tǒng)中可使用 cat /proc/misc
命令查看系統(tǒng)中的雜項設備。注冊雜項設備的步驟:
-
1.填充設備操作集結構體
struct file_operations
; -
2.填充雜項設備結構體
struct miscdevice
; -
3.使用函數(shù)
misc_register
注冊雜項設備; -
4.使用函數(shù)
misc_deregister
卸載雜項設備;
上面三步可使用下面函數(shù)直觀用表達,即:
static struct file_operations xxx_fops{.owner = THIS_MODULE, .read = xxx_read, ....
};
struct miscdevice xxx_dev{.minor = MISC_DYNAMIC_MINOR, .name = "xxx", .fops = &xxx_fops
};
static int __init xxx_init(void) //驅(qū)動入口函數(shù)
{int ret;printk(KERN_EMERG "xxx_init\r\n");ret = misc_register(&xxx_dev);//注冊雜項設備if(ret<0){printk( "misc_register failed\r\n");return -1;}printk( "misc_register ok\r\n");return 0;
}
static void __exit xxx_exit(void) //驅(qū)動出口函數(shù)
{printk(KERN_EMERG "xxx_exit\r\n");misc_deregister(&xxx_dev); //卸載雜項設備
}
module_init(xxx_init); //注冊入口函數(shù)
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
具體實現(xiàn)注冊一個雜項設備的示例代碼如下:
#include <linux/kernel.h>
#include <linux/init.h> //初始化頭文件
#include <linux/module.h> //最基本的文件,支持動態(tài)添加和卸載模塊。
#include <linux/miscdevice.h> //注冊雜項設備頭文件
#include <linux/fs.h> //注冊設備節(jié)點的文件結構體
#include <linux/uaccess.h>// 打開雜項設備
int _open(struct inode *inode,struct file*file)
{printk(KERN_EMERG"hello misc");return 0;
}// 關閉雜項設備
int close(struct inode * inode, struct file *file)
{printk(KERN_EMERG"close");return 0;
}// 讀取雜項設備中的數(shù)據(jù)
ssize_t misc_read (struct file *file, char __user *buff, size_t size, loff_t *loff)
{char kbuff[32] = "kernel";if(copy_to_user(buff,kbuff,strlen(kbuff)) != 0) // 將內(nèi)核中的數(shù)據(jù)給應用{printk("copy_to_user error\r\n");return -1;} printk(KERN_EMERG"copy_to_user is successful\r\n");return size;
}// 寫入數(shù)據(jù)到雜項設備中
ssize_t misc_write (struct file *file, const char __user *buff, size_t size, loff_t *loff)
{char kbuff[32] ;if(copy_from_user(kbuff,buff,size)!= 0) // 從應用那兒獲取數(shù)據(jù){printk("copy_from_user error\r\n");return -1;} printk(KERN_EMERG"copy_from_user data:%s\r\n",kbuff);return size;
}// 設備文件描述集
struct file_operations misc_fops ={.owner = THIS_MODULE,.open = misc_open,.release = close,.read = misc_read,.write = misc_write
};struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "hello_misc", // 雜項設備名 注冊成功后會在 /dev目錄下顯示.fops = &misc_fops
};// 驅(qū)動的入口函數(shù)
static int __init misc_init(void)
{int ret = 0;ret = misc_register(&misc_dev);if(ret < 0)printk(KERN_EMERG"misc register is error\r\n.");elseprintk(KERN_EMERG"misc register is seccussful\r\n.");return 0;
}//驅(qū)動的出口函數(shù)
static void __exit misc_exit(void)
{misc_deregister(&misc_dev);printk(KERN_EMERG"baibai\r\n");
}module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhouxianjie0716@qq.com");
編譯傳送到開發(fā)板上后,先試用insmod +驅(qū)動名.ko
掛載驅(qū)動,其結果為:
上述驅(qū)動代碼的測試代碼如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc,char *argv[])
{char filePath[] = "/dev/hello_misc";// 打開文件int fd = open(filePath,O_RDWR);if(fd < 0){printf("opening is failed.\n");return -1;}elseprintf("opening is successful.\n");// 讀取char buff1[32],buff2[32] = "hello this is app";read(fd,buff1,sizeof(buff1));printf("buff1 is %s.\n",buff1);// 寫入write(fd,buff2,sizeof(buff2)); close(fd);return 0;
}
使用./+程序名
運行測試代碼后,得結果如下:
驅(qū)動模塊傳參
總所周知,應用程序傳參是通過shell終端傳,只要將main函數(shù)按照下面格式書寫即可完成傳參操作
int main(int argc ,char *argv[])
{return 0;
}
相比之下,驅(qū)動模塊傳遞參數(shù)需要借助其他函數(shù)完成傳參操作:
1. 傳遞單個參數(shù)給內(nèi)核
module_param(name, type, perm)
參數(shù)解釋:
-
name:參數(shù)名,既是外部參數(shù)名,又是內(nèi)部參數(shù)名。
-
type:參數(shù)的數(shù)據(jù)類型,可取int、charp等。
-
perm:訪問權限。八進制,如:0777。0表示該參數(shù)在文件系統(tǒng)中不可見。
注意:傳遞字符作為參數(shù)時數(shù)據(jù)類為charp
,而不是char
.
2.傳遞數(shù)組給內(nèi)核
module_param_array(name, type, nump, perm)
- module_param_array(name,type,nump,perm)
- name:數(shù)組參數(shù)名,既是外部參數(shù),又是內(nèi)部參數(shù)
- type:參數(shù)的數(shù)據(jù)類型
- nump:終端傳給數(shù)組的實際元素個數(shù)(指針變量)
- perm:訪問權限,0644。0表示該參數(shù)在文件系統(tǒng)中不可見
3.傳遞字符串給內(nèi)核
module_param_string(name, string, len, perm)
- name:參數(shù)名,外部參數(shù)名
- string:內(nèi)部參數(shù)名(內(nèi)部字符數(shù)組名)
- len:數(shù)組長度
- perm:訪問權限,0644。0表示該參數(shù)在文件系統(tǒng)中不可見
傳遞參給內(nèi)核的作用:
- 1.設置驅(qū)動的相關參數(shù),如:設備數(shù)量、設備緩沖區(qū)大小等等。
- 2.可進行安全校驗,放置驅(qū)動被他人盜用。
說明:
參數(shù)傳遞的適用時機為 加載驅(qū)動到內(nèi)核時,命令形式為:insmod +驅(qū)動名.ko +參數(shù)1名=參數(shù)值 參數(shù)2名=參數(shù)值 ……
。例如下面示例中使用insmod file.ko date=12
傳遞參數(shù):
#include <linux/module.h>
#include <linux/init.h>// 保存參數(shù)
static int date;
static int date1;
static int data[5];
static int count;
static char str[32];
static char *strData;// 傳遞單個參數(shù)
module_param(date,int,S_IRUGO|S_IWUSR); // 可讀可寫
module_param(date1,int,S_IRUGO); // 可讀可寫
module_param(strData,charp,S_IRUGO); // 可讀// 傳遞多個參數(shù)
module_param_array(data,int,&count,S_IRUGO); // 可讀
module_param_string(str,str,sizeof(str),S_IRUGO); // 可讀// 驅(qū)動的入口函數(shù)
static int __init dev_init(void)
{int i=0;if(strcmp(str,"myTest")!=0){printk("dev_init error\r\n");return -1;}printk("---------------------------------------\r\n");printk(KERN_EMERG"dev_init is successful!\r\n");for(i=0;i<count;++i)printk("data[%d] = %d \r\n",i,data[i]);printk("str:%s strData:%s\r\n",str,strData);printk("date:%d count:%d\r\n",date,count);data[1] = 111;date = 10;date1 = 111;return 0;
}//驅(qū)動的出口函數(shù)
static void __exit dev_exit(void)
{int i=0;for( i=0;i<count;++i)printk("data[%d] = %d \r\n",i,data[i]);printk("date:%d count:%d\r\n",date,count);printk(KERN_EMERG"dev_exit is successful\r\n");
}module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhouxianjie0716@qq.com");
注冊字符設備
步驟一:驅(qū)動初始化,需要申請設備號,初始化并且注冊cdev結構體,初始化硬件;
可使用動態(tài)申請或靜態(tài)申請設備號,其中動態(tài)申請設備號一般在235-255,靜態(tài)申請則一般由用戶手動輸入。
靜態(tài)申請的函數(shù)原型為:
int register_chrdev_region(dev_t, unsigned, const char *);
參數(shù)含義:
- from: 自定義的 dev_t 類型設備號。
- count: 申請設備的數(shù)量。
- name: 申請的設備名稱。
函數(shù)返回值:申請成功返回 0,申請失敗返回負數(shù)。
動態(tài)申請的函數(shù)原型為:
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
參數(shù)含義:
- dev : 會將申請完成的設備號保存在 dev 變量中。
- baseminor: 次設備號可申請的最小值。
- count: 申請設備的數(shù)量。
- name: 申請的設備名稱。
函數(shù)返回值:申請成功返回 0,申請失敗返回負
Linux 內(nèi)核中將字符設備抽象成一個具體的數(shù)據(jù)結構 (struct cdev), 我們可以理解為字符設備對象,cdev 記錄了字符設備號、內(nèi)核對象、文件操作 file_operations 結構體(設備的打開、讀寫、關閉等操作接口)等信息:
struct cdev {struct kobject kobj; //內(nèi)嵌的內(nèi)核對象.struct module *owner; //該字符設備所在的內(nèi)核模塊的對象指針. const struct file_operations *ops; //該結構描述了字符設備所能實現(xiàn)的方法,是極為關鍵的一個結構體.struct list_head list; //用來將已經(jīng)向內(nèi)核注冊的所有字符設備形成鏈表. dev_t dev; //字符設備的設備號,由主設備號和次設備號構成. unsigned int count; //隸屬于同一主設備號的次設備號的個數(shù).
};
初始化設備描述集cdev結構體的函數(shù)原型為:
void cdev_init(struct cdev *, const struct file_operations *);
參數(shù)含義:
- 參數(shù)1:表示是抽象設備結構體;
- 參數(shù)2:表示文件操作集;
注冊設備到內(nèi)驅(qū)使用下面函數(shù)
int cdev_add(struct cdev *, dev_t, unsigned);
參數(shù)含義:
- 參數(shù)1:為要添加的 struct cdev 類型的結構體
- 參數(shù)2:為申請的字符設備號
- 參數(shù)3:為和該設備關聯(lián)的設備編號的數(shù)量
若函數(shù)在內(nèi)核中添加成功返回 0,添加失敗返回負數(shù)。
步驟二:構建設備文件操作描述集file_operations
也就是read、write、open、close等函數(shù)。
步驟三:生成并且添加設備節(jié)點
- 手動添加設備節(jié)點:也就是在加載驅(qū)動到內(nèi)核時添加,即
mknod + 路徑/設備名 +設備類型 + 主設備號 + 次設備號
- 自動添加設備節(jié)點:初始化內(nèi)核時,使用函數(shù)自動添加。即先試用函數(shù)
class_create
創(chuàng)建一個類,在使用函數(shù)device_create
創(chuàng)建并且添加設備節(jié)點。
class_create
函數(shù)說明:
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \ __class_create(owner, name, &__key); \
})
函數(shù)作用:
用于動態(tài)創(chuàng)建設備的邏輯類,并完成部分字段的初始化,然后將其添加進 Linux 內(nèi)核系統(tǒng)。
參數(shù)含義:
- owner:指向函數(shù)即將創(chuàng)建的這個 struct class 的模塊。一般為 THIS_MODULE。
- name:代表即將創(chuàng)建的 struct class 變量的名字。
返回值:struct class * 類型的類。
device_create
函數(shù)說明:
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt,...);
函數(shù)作用:
用來在 class 類中下創(chuàng)建一個設備屬性文件,udev 會自動識別從而進行設備節(jié)點的創(chuàng)建。
參數(shù)含義:
- cls:指定所要創(chuàng)建的設備所從屬的類。
- parent:指定該設備的父設備,如果沒有就指定為 NULL。
- devt:指定創(chuàng)建設備的設備號。
- drvdata:被添加到該設備回調(diào)的數(shù)據(jù),沒有則指定為 NULL。
- fmt:添加到系統(tǒng)的設備節(jié)點名稱。
返回值:struct device * 類型結構體的設備
步驟四:注銷字符設備驅(qū)動,
- 步驟一:使用函數(shù)
unregister_chrdev_region
釋放設備號
void unregister_chrdev_region(dev_t, unsigned)
該函數(shù)只有一個參數(shù),為要刪除設備的設備號,并且函數(shù)無返回值。
- 步驟二:使用函數(shù)
cdev_del
步驟二:刪除設備操作集卸載cdev
void cdev_del(struct cdev *);
該函數(shù)只有一個參數(shù),為要刪除的 struct cdev 類型的結構體,并且函數(shù)無返回值。
- 步驟三:使用函數(shù)
device_destroy
卸載設備
void device_destroy(struct class *cls, dev_t devt);
用來刪除 cls 類中的devt設備屬性文件,udev 會自動識別從而進行設備節(jié)點的刪除。
- 步驟四:使用函數(shù)
class_destroy
刪除類class
void class_destroy(struct class *cls);
該函數(shù)只有一個參數(shù),為要刪除的類,并且函數(shù)無返回值。
示例代碼
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>#define DEVICE_NUMBER 1 // 設備數(shù)量
#define DEVICE_SNAME "schrdev" // 靜態(tài)申請時設備名
#define DEVICE_ANAME "achrdev" // 動態(tài)申請時設備名
#define DEVICE_MINOR_NUM 0 // 次設備號起始地址
#define DEVICE_CLASS_NAME "myTestClass" // 類名
#define DEVICE_MYNAME "mytest"// 打開設備
int chrdev_open(struct inode*inode,struct file*file)
{printk("chrdev_open is opened\r\n");return 0;
}// 保存設備號 其中前12位為主設備號 后20位為次設備號
static dev_t dev_num;// 定義主設備號 次設備號
static int major_num,minor_num; // 設備信息描述集
struct cdev cdev;// 類描述集
struct class *cls;// 設備描述集
struct device*device;// 傳遞單個參數(shù)
module_param(major_num,int,S_IRUGO); // 可讀
module_param(minor_num,int,S_IRUGO); // 可讀// 文件操作集
struct file_operations chrdev_opr = {.owner = THIS_MODULE,.open = chrdev_open
};// 驅(qū)動的入口函數(shù)
static int __init dev_init(void)
{int ret;/* 步驟一:申請設備號 */// 有傳遞主設備號就靜態(tài)申請if(major_num){ dev_num = MKDEV(major_num, minor_num);//將主設備號 次設備號合并成設備號//參數(shù)分別表示 設備號 設備數(shù)量 設備名稱ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); if(ret < 0){printk("dev_init error\r\n");return -1;}} // 否則就動態(tài)申請else{// 參數(shù):設備號 次設備號起始地址 設備數(shù)量 設備名稱ret = alloc_chrdev_region(&dev_num , DEVICE_MINOR_NUM, DEVICE_NUMBER, DEVICE_ANAME);if(ret < 0){printk("dev_init error\r\n");return -1;}// 獲取主設備號 次設備號major_num = MAJOR(dev_num);minor_num = MINOR(dev_num);}printk("---------------------------------------\r\n");printk("dev_num:%d major_num:%d minor_num:%d\r\n",dev_num,major_num,minor_num);/* 步驟二:初始化設備 */// 初始化cdevcdev.owner = THIS_MODULE;// 初始化設備 cdev_init(&cdev, &chrdev_opr);/* 步驟三:注冊設備到內(nèi)核 *///添加(注冊)到內(nèi)核 參數(shù): 設備 設備號 設備數(shù)量cdev_add(&cdev, dev_num, DEVICE_NUMBER);/* 步驟四:先創(chuàng)建類再自動創(chuàng)建添加設備名稱*/// 創(chuàng)建類 參數(shù)1: 類的歸屬 參數(shù)2: 類名cls = class_create(THIS_MODULE,DEVICE_CLASS_NAME);// 創(chuàng)建設備 參數(shù)1: 歸屬到類 參數(shù)2:設備的父設備 參數(shù)3:設備號 參數(shù)4:添加到設備的回調(diào)數(shù)據(jù) 參數(shù)5:設備名device = device_create(cls,NULL,dev_num,NULL,DEVICE_MYNAME);printk("auto add device name\r\n");return 0;
}//驅(qū)動的出口函數(shù)
static void __exit dev_exit(void)
{/* 步驟一:注銷設備號 參數(shù):設備號 設備數(shù)量 */ unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);/* 步驟二:刪除設備操作集 */cdev_del(&cdev);/* 步驟三:刪除設備*/device_destroy(cls,dev_num);/* 步驟四: 刪除類*/class_destroy( cls);printk(KERN_EMERG"dev_exit is successful\r\n");
}module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhouxianjie0716@qq.com");
使用加載驅(qū)動到內(nèi)核insmod file.ko命令,得結果
在測試驅(qū)動前需要使用mknod /dev/mytest c 236 0
創(chuàng)建設備文件,命令格式為:mknod + /路徑/設備名稱 +設備類型+主設備號 + 次設備號
。
然后就可寫一個應用程序來測試我們的字符驅(qū)動是否成功注冊,測試源碼如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc,char* argv[])
{char name[] = "/dev/mytest";int fd = open(name,O_RDONLY); // 僅讀if(fd < 0){printf("open is failed\n");return -1;}close(fd);return 0;
}
執(zhí)行測試程序后結果如下:
注意:在運行測試程序時,一定要先創(chuàng)建驅(qū)動文件,否則就會報段錯誤,具體如下:
雜項設備與字符設備的比較:
- 雜項設備的主設備號固定為10,而字符設備的主設備號需要創(chuàng)建。
- 雜項設備的創(chuàng)建相對簡單,只需要填充設備操作集結構體、雜項設備結構體再注冊即可。而字符設備需要經(jīng)過 申請設備號、初始化并且注冊cdev結構體、初始化硬件、構建設備文件操作描述集、生成并且添加設備節(jié)點。
- 雜項設備與字符設備都需要構建文件操作描述集。(應用層調(diào)用驅(qū)動的核心)