文章目錄
設備號
常用接口
設備號申請/釋放
register_chrdev_region函數
alloc_chrdev_region函數
unregister_chrdev_region函數
其它宏定義
字符設備的定義
cdev_alloc函數
字符設備的綁定
cdev_init函數
字符設備注冊/注銷
cdev_add函數
cdev_del函數
file_operations結構
實現一個簡單的字符設備驅動
以前的接口
register_chrdev函數
unregister_chrdev函數
測試流程及結果
總結
點擊下方閱讀原文可訪問文中超鏈接
設備號
設備號(統稱,由主設備號和次設備號組成)是一種資源,更是應用層與驅動層之間的紐帶,設備號由主設備號和次設備號組成,主設備號用于區分是哪類設備,次設備號用于區分某一類設備中的各個子設備;設備號的分配規則是從大往小分配,其中某些設備號是系統已經約定好了的預分配給指定設備;
當向內核申請了設備號后,就可以在/proc/devices
文件中看到對應的主設備號及設備名,此時使用mknod
也可以在/dev
目錄生成設備節點,不過這個節點是個空節點,沒有任何用;設備號被注銷后,/proc/devices
中也會自動刪除,如下:
常用接口
設備號申請/釋放
位于頭文件:include/linux/fs.h
register_chrdev_region函數
向內核申請指定的設備號,如果指定的設備號已存在則會申請失敗。
int register_chrdev_region(dev_t, unsigned, const char *);
示例用法:
//MKDEV是一個宏,用于將主次設備號合并成設備號
//第二個參數表示申請多少個設備號
//第三個參數是設備名
register_chrdev_region(MKDEV(247,0),1,"test_char_dev");
alloc_chrdev_region函數
向內核申請設備號,并且由內核自動分配可用的設備號。
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
示例用法:
//devno存儲申請的設備號
//第二個參數表示次設備號的起始值
//第三個參數表示申請多少個設備號
//第四個參數是設備名
alloc_chrdev_region(&devno,0,1,"test_char_dev");
unregister_chrdev_region函數
用于注銷由register_chrdev_region
和alloc_chrdev_region
申請的設備號,前面也說了設備號是一種資源,所以要養成良好的習慣,不用就要歸還。
void unregister_chrdev_region(dev_t, unsigned);
示例用法:
//devno申請的設備號
//第二個參數表示申請的個數,申請了多少就要注銷多少
unregister_chrdev_region(devno,1);
其它宏定義
位于頭文件:include/linux/kdev_t.h
用于提取設備號的主設備號。
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
用于提取設備號的次設備號。
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
字符設備的定義
cdev_alloc函數
位于頭文件:include/linux/cdev.h
使用動態內存的方式定義一個字符設備。
struct cdev *cdev_alloc(void);
或者直接定義為靜態全局變量
static struct cdev chr_dev;
字符設備的綁定
cdev_init函數
將一個字符設備與file_operations
結構綁定起來。
void cdev_init(struct cdev *, const struct file_operations *);
字符設備注冊/注銷
cdev_add函數
向內核注冊字符設備。
int cdev_add(struct cdev *, dev_t, unsigned);
cdev_del函數
注銷一個字符設備。
void cdev_del(struct cdev *);
file_operations結構
位于頭文件:include/linux/fs.h
這個結構體包含了一系列的函數指針,基本上每一個函數指針都對應了應用層一個接口函數,比如調用應用層的open
函數時,最終就會調用到這個結構體里面的open
函數指針。我們就可以通過實現一些需要的函數來操作硬件。
示例代碼:
static int test_open (struct inode *inode, struct file *file)
{
return 0;
}
static int test_close (struct inode *inode, struct file *file)
{
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_open,
.release = test_close,
};
實現一個簡單的字符設備驅動
一個簡單的字符設備驅動大致分為以下幾個步驟:
向內核申請設備號
定義一個字符設備對象
綁定字符設備對象與file_operations結構
向內核注冊字符設備
實現file_operations結構體內的部分接口
簡單源碼如下(注意未實現各種錯誤及邊界檢查,實際應用時應完善,以保證程序的健壯性):
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#define DEBUG(fmt, ...) \
do{ \
if(if_debug) \
printk(KERN_INFO "DEBUG > " fmt, ##__VA_ARGS__); \
}while(0)
bool if_debug = false;
/*定義一個bool類型的變量,作為一個模塊入參,在裝載模塊時可賦值*/
module_param(if_debug, bool, S_IRUSR);
#define DEV_COUNT 1
/*設備號*/
static dev_t devno;
/*定義一個字符設備*/
static struct cdev *char_dev;
static int test_open (struct inode *inode, struct file *file)
{
DEBUG("open test..\r\n");
return 0;
}
static int test_close (struct inode *inode, struct file *file)
{
DEBUG("close test..\r\n");
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_open,
.release = test_close,
};
static int __init test_init(void)
{
/*申請設備號*/
alloc_chrdev_region(&devno,0,DEV_COUNT,"test_char_dev");
/*使用動態內存定義一個字符設備*/
char_dev = cdev_alloc();
/*綁定字符設備和file_operations結構*/
cdev_init(char_dev,&fops);
/*注冊字符設備*/
cdev_add(char_dev,devno,DEV_COUNT);
return 0;
}
static void __exit test_exit(void)
{
/*注銷一個字符設備*/
cdev_del(char_dev);
/*注銷申請的設備號*/
unregister_chrdev_region(devno,DEV_COUNT);
}
/*此宏聲明內核模塊的初始化入口點*/
module_init(test_init);
/*此宏聲明內核模塊的退出入口點*/
module_exit(test_exit);
/*聲明開源協議*/
MODULE_LICENSE("GPL");
/*聲明作者*/
MODULE_AUTHOR("wei");
/*聲明模塊的描述*/
MODULE_DESCRIPTION("this is a test driver");
以前的接口
同樣是創建字符設備,但是內核還有兩個接口,可以大大簡化上面的代碼。
register_chrdev函數
此函數可一步到位,完成了設備號的申請,字符設備與file_operations
結構的綁定以及字符設備的注冊全部操作,只不過這個函數會自動申請256個主設備號相同的設備號(次設備號為0到255),如果沒有這么多同類子設備的話會造成空間的浪費。
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
示例代碼:
//第一個參數為主設備號,如果為0則內核自動分配
//第二個參數為設備名
//第三個參數為file_operations結構體
register_chrdev(247,"test_char_dev",fops);
unregister_chrdev函數
用于注銷由register_chrdev
申請的資源。
void unregister_chrdev(unsigned int major, const char *name)
示例用法:
//第一個參數為主設備號
//第二個參數為設備名
unregister_chrdev(247,"test_char_dev");
第二種更簡潔的寫法:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#define DEBUG(fmt, ...) \
do{ \
if(if_debug) \
printk(KERN_INFO "DEBUG > " fmt, ##__VA_ARGS__); \
}while(0)
bool if_debug = false;
/*定義一個bool類型的變量,作為一個模塊入參,在裝載模塊時可賦值*/
module_param(if_debug, bool, S_IRUSR);
#define CHRDEV_NAME "test_char_dev"
/*主設備號*/
static dev_t major;
static int test_open (struct inode *inode, struct file *file)
{
DEBUG("open test..\r\n");
return 0;
}
static int test_close (struct inode *inode, struct file *file)
{
DEBUG("close test..\r\n");
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_open,
.release = test_close,
};
static int __init test_init(void)
{
major = register_chrdev(0,CHRDEV_NAME,&fops);
return 0;
}
static void __exit test_exit(void)
{
unregister_chrdev(major,CHRDEV_NAME);
}
/*此宏聲明內核模塊的初始化入口點*/
module_init(test_init);
/*此宏聲明內核模塊的退出入口點*/
module_exit(test_exit);
/*聲明開源協議*/
MODULE_LICENSE("GPL");
/*聲明作者*/
MODULE_AUTHOR("wei");
/*聲明模塊的描述*/
MODULE_DESCRIPTION("this is a test driver");
驅動寫好后,還需要讓應用程序能夠使用,而應用程序是通過設備節點來訪問的,所以接下來創建設備節點,以讓應用程序可以訪問寫好的驅動。使用mknod
命令創建設備節點:
mknod 設備名 設備類型 主設備號 次設備號
示例用法:
mknod /dev/test_char_dev c 247 0
此程序沒有實現任何的硬件操作,下面寫個簡單的應用程序測試一下驅動是否能正常工作,因為只實現了open
和release
接口,所以在應用程序中也只能使用這兩個接口。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
int fd;
fd = open("/dev/test_char_dev",O_RDONLY);
if(fd < 0)
{
perror("open");
return -1;
}
close(fd);
return 0;
}
測試流程及結果
# 裝載驅動
insmod test.ko if_debug=1
# 查看申請的設備號
cat /proc/devices
# 創建設備節點(主設備號由上條命令查看,次設備號與驅動程序一致)
mknod /dev/test_char_dev c 247 0
# 執行應用程序
./app_test
# 運行結果會打印出驅動程序中的兩個調試信息
DEBUG > open test..
DEBUG > close test..
# 卸載驅動
rmmod test.ko
# 刪除設備節點
rm /dev/test_char_dev
總結
我根據自己的理解,畫了一個關系圖。
測試源碼獲取:點我