19 字元裝置驅動基礎
阿新 • • 發佈:2018-11-25
字元裝置驅動基礎
裝置驅動通常是給使用者程序來呼叫的
最常用的是裝置驅動裡實現字元裝置驅動,實現後在”/dev/”目錄裡提供一個裝置檔案,然後使用者程序就可以通過操作該裝置檔案來呼叫驅動
如pc上的uart裝置檔案:
crw-rw—- 1 root dialout 4, 64 Jun 8 09:20 /dev/ttyS0
crw-rw—- 1 root dialout 4, 65 Jun 8 09:20 /dev/ttyS1
crw-rw—- 1 root dialout 4, 66 Jun 8 09:20 /dev/ttyS2
crw-rw—- 1 root dialout 4 , 67 Jun 8 09:20 /dev/ttyS3
第一個字元'c'表示字元裝置檔案,也就是此裝置檔案對應著一個字元裝置驅動
裝置檔名字不重要,不管改成什麼名字,功能還是可以用的
裝置檔案的裝置號才是最重要的,裝置號由主裝置號和次裝置號組成
如:ttyS0的主裝置號是4,次裝置號是64
主裝置號通常表示一個字元裝置驅動,上面4個uart裝置它們驅動方法應是一樣的,可以共用一個驅動
在驅動裡可通過次裝置號來區分不同的硬體介面
裝置驅動裡在初始化時也需指定使用哪些裝置號
當用戶程序操作裝置檔案時,核心會根據裝置檔案的裝置號找到對應的裝置驅動,從而讓使用者程序通過核心與裝置驅動建立聯絡,實現呼叫驅動裡實現的功能
裝置號很重要,在系統裡是不可以重用的資源,裝置號不能重用
在”include/linux/kdev_t.h”標頭檔案裡有提供裝置號的操作巨集:
#define MINORBITS 20 //次裝置號位數
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //獲取主裝置號
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //獲取次裝置號
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //由主裝置號和次裝置號生成一個裝置號
裝置號使用前需要向核心申請,使用完後需返還核心
裝置號操作方法:
#include <linux/fs.h>
//靜態申請指定的裝置號,from指裝置號(需已指定主裝置和次裝置號),count指使用該驅動有多少個裝置(次裝置號),name為裝置名(用於檢視用,長度不能超過64位元組)
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//動態申請裝置號,由核心分配沒有使用的主裝置號,分配好的裝置號存在dev(不需要初始化),baseminor指次裝置號從多少開始,count指裝置個數,name為裝置名
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
//釋放裝置號,from指裝置號,count指裝置數
void unregister_chrdev_region(dev_t from, unsigned count);
簡單的事例程式碼1(申請裝置號)(xxx.c):
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#define MYMA 1234
#define MYMI 3344
#define COUNT 3
dev_t devid;//用於存放裝置號
static int __init test_init(void)
{
int ret;
devid = MKDEV(MYMA, MYMI);//生成一個裝置號
ret = register_chrdev_region(devid, COUNT, "mydev"); //申請裝置號
if (ret < 0)
goto err0;
//執行到這裡,則有三個裝置號(1234, 3344), (1234, 3345), (1234, 3346)
return 0;
err0:
return ret;
}
static void __exit test_exit(void)
{
unregister_chrdev_region(devid, COUNT);//使用完後需回收裝置號
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
Makefile檔案:
obj-m += xxx.o
KSRC := /目錄路徑/orangepi_sdk/source/linux-3.4.112/
export ARCH := arm
export CROSS_COMPILE := arm-linux-gnueabihf-
all :
make -C $(KSRC) modules M=`pwd`
.PHONY : clean
clean :
make -C $(KSRC) modules clean M=`pwd`
編譯後,載入模組後,可通過”cat /proc/devices”檢視系統裡主裝置號的使用狀況
在linux核心裡使用”struct cdev”結構體型別的一個物件來描述一個字元裝置驅動
include <linux/cdev.h>
struct cdev {
struct kobject kobj;//核心用於管理字元裝置驅動,kobject就是核心裡最底層的類(核心裡會自動管理此成員)
struct module *owner;//通常設為THIS_MODULE,用於防止驅動在使用中時解除安裝驅動模組
const struct file_operations *ops;//怎樣操作(vfs),也就是實現當用戶程序進行的open/read/write等操作時,驅動裡對應的操作
struct list_head list;//核心連結串列節點(核心裡自動管理此成員)
dev_t dev;//裝置號
unsigned int count;//裝置數
};
同時核心裡也提供對cdev物件操作的函式:
#include <linux/cdev.h>
void cdev_init(struct cdev *, const struct file_operations *);//初始化cdev物件
struct cdev *cdev_alloc(void);//動態分配一個cdev物件
int cdev_add(struct cdev *, dev_t, unsigned);//設定cdev物件使用裝置號及裝置個數,再把cdev物件增加到核心裡,讓它工作起來
void cdev_del(struct cdev *);//從核心裡移除cdev物件
字元裝置驅動實現的基本流程:
1.申請裝置號:
register_chrdev_region(...);
2.宣告一個全域性的cdev物件:
struct cdev mycdev;
3.宣告一個全域性的file_operations的檔案操作物件:
struct file_operations fops = {
.owner = THIS_MODULE,
.read = 讀函式地址,
....,
};
4.初始化cdev物件,並把fops物件與cdev物件關聯起來:
cdev_init(&mycdev, &fops);//mycdev.ops = &fops;
mycdev.owner = THIS_MODULE;
5.把cdev物件加入核心裡cdev_map(字元裝置驅動的雜湊表),並指定該驅動物件的裝置號:
cdev_add(&mycdev, 裝置號, 次裝置號的個數);
6.解除安裝模組時,要把裝置驅動物件從核心裡移除,並把裝置號反註冊:
unregister_chrdev_region(..);
cdev_del(&mycdev);
事例程式碼2(xxx.c):
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#define MYMA 1234
#define MYMI 3344
#define COUNT 3
dev_t devid;//用於存放裝置號
struct cdev mycdev;
int myopen(struct inode *ind, struct file *fl)
{
printk("in %s\n", __func__);
return 0;
}
ssize_t myread(struct file *fl, char *__user buf, size_t len, loff_t *off)
{
printk("in %s\n", __func__);
return len;
}
ssize_t mywrite(struct file *fl, const char __user *buf, size_t len, loff_t *off)
{
printk("in %s\n", __func__);
return len;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = myopen,
.read = myread,
.write = mywrite,
};
static int __init test_init(void)
{
int ret;
devid = MKDEV(MYMA, MYMI);//生成一個裝置號
ret = register_chrdev_region(devid, COUNT, "mydev");
if (ret < 0)
goto err0;
cdev_init(&mycdev, &fops);
mycdev.owner = THIS_MODULE;
ret = cdev_add(&mycdev, devid, COUNT);//將cdev加入核心
if (ret < 0)
goto err1;
return 0;
err1:
unregister_chrdev_region(devid, COUNT);
err0:
return ret;
}
static void __exit test_exit(void)
{
unregister_chrdev_region(devid, COUNT);//使用完後需回收裝置號
cdev_del(&mycdev);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
編譯載入驅動模組後,需要用”mknod /dev/裝置檔名 c 主裝置號 次裝置號”來建立裝置檔案與裝置號相對應:
(以後操作裝置檔案時,就可以觸發相應的裝置號所對應的裝置驅動)
mknod /dev/mydev0 c 1234 3344
mknod /dev/mydev1 c 1234 3345
mknod /dev/mydev2 c 1234 3346
然後可以寫應用程式來操作裝置檔案,也可以用命令來簡單地測試:
cat /dev/mydev0 //會觸發驅動裡的open,read函式
echo "kkk" > /dev/mydev0 //會觸發驅動裡的open,write函式