基礎概念:
設備號 : 分爲主設備號和次設備號。設備號是16bit, 高8bit爲主設備號,低8bit爲次設備號。當我們創建一個設備節點時需要指定主設備號和次設備號。應用程序通過名稱訪問設備,而設備號指定了對應的驅動程序和對應的設備。主設備號標識設備對應的驅動程序,次設備號由內核使用,用於確定設備節點所指設備。你可以通過/proc/devices文件來查看系統設備的主設備號。
設備節點: Linux中設備節點是通過“mknod”命令來創建的。一個設備節點其實就是一個文件,Linux中稱爲設備文件。一般都創建在dev/目錄下。
在userspace中可以打開設備節點,並通過驅動向設備節點寫入數據。具體怎麼實現呢?
1.kernel層的實現
a) 找到一個設備號devno,可以動態申請,也可以靜態設定,假設靜態設定爲major,minor,通過宏MKDEV(major,minor)來生成devno
b) 構建對設備的操作函數集file_opreation結構體,裏面包含了的設備的操作:open、read、write、release、ioctl等
c) 構建cdev結構體,裏面填充兩個主要成員dev(設備號)、file_operation(對設備的操作)
d) 把cdev添加到cdev鏈表中:cdev_init、cdev_add
具體代碼:
首先, 內核在初始化的時候會調用initcall.init中保存的一份函數指針,並在初始化完成之後釋放整個init區段。
當需要把函數xxx_init放到initcall.init區段的函數指針表中時,只需要聲明: core_initcall(xxx_init);即可。
module_exit(xxx_exit)和前面的過程相似,在退出的時候會執行所有放在exit表中的函數。
所以一個驅動,最先執行的代碼就是放在init列表裏面的xxx_init函數。
我們看看這個函數做了什麼:
static int __init xxx_init(void)
{
dev_t dev;
struct xxx_dev xxxdriver = kzalloc(sizeof(struct xxx_dev) + 5, GFP_KERNEL); // 先定義一個driver
// 給driver中的變量賦值
xlogdriver->buf = kzalloc(XLOGBUF_SIZE, GFP_KERNEL);
xlogdriver->num = 1;
xlogdriver->name = "xxx";
xlogdriver->free_size = XLOGBUF_SIZE;
alloc_chrdev_region(&dev, xlogdriver->minor_start, xlogdriver->num, xlogdriver->name);
xlogdriver->cdev = cdev_alloc();
ret = xxx_setup_cdev(dev); // 然後調用setup函數!!!
}
// 構建對設備的操作函數集file_opreation結構體
static const struct file_operations xxxfops = {
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
.poll = xxx_poll,
.open = xxx_open,
.release = xxx_close
};
static int xxx_setup_cdev(dev_t devno)
{
cdev_init(xxxdriver->cdev, &xxxfops); // 初始化cdev
xxxdriver->cdev->owner = THIS_MODULE;
xxxdriver->cdev->ops = &xxxfops; // cdev的主要成員,對設備節點的操作
err = cdev_add(xxxdriver->cdev, devno, 1); // 把cdev添加到cdev鏈表中
xxxdriver->xxx_class = class_create(THIS_MODULE, "xxx");
xlogdriver->xlog_dev = device_create(xlogdriver->xxx_class,NULL, devno, (void *)xlogdriver, "xlog");
return 0;
}
然後重點實現的函數就是file_operations xxxfops中構建的函數xxx_read、xxx_write、xxx_poll、xxx_open和xxx_close。
實現完成之後userspace就可以通過這些函數去對xxx這個設備節點進行操作了。
eg:
static ssize_t xxx_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int err = 0;
size_t copy_bytes;
u64 temp = count;
mutex_lock(&xxxdriver->xxx_mutex); // 寫入之前先上鎖
if (xxxBUF_SIZE < xxxdriver->writeindex + count) {
copy_bytes = xxxBUF_SIZE - xxxdriver->writeindex;
err = copy_from_user(xxxdriver->buf + xxxdriver->writeindex, buf, copy_bytes); // 從userspace的buf中讀取數據寫入xxxdriver->buf
err = copy_from_user(xxxdriver->buf, buf + copy_bytes, count - copy_bytes);
xxxdriver->writeindex = count - copy_bytes;
} else {
err = copy_from_user(xxxdriver->buf + xxxdriver->writeindex, buf, count);
xxxdriver->writeindex += count;
}
xxxdriver->free_size -= count; // 更新剩餘空間
mutex_unlock(&xxxdriver->xxx_mutex); // 寫完之後解鎖
wake_up_interruptible(&xxxdriver->wait_q);
return count;
}
2. userspace的操作
int fd=open("/dev/xxx",O_RDWR)來打開設備文件,此設備對應有一個設備號,這是我們識別驅動和設備的橋樑。
打開 /dev/xxx時,根據設備號,在cdev鏈表中找到cdev這個結構體,cdev裏面包含了file_operation結構體,有設備的各種操作,打開時就調用裏面的.open 函數。
具體代碼:
例如此時app有一些數據需要寫入設備節點:
void wirte_to_xxx(char* str){
int fd = open("/dev/xxx",O_RDWR); // 先打開設備節點,此處最終調用的就是xxx_open
write(fd, str, strlen(str)); // 寫入數據, 此處最終調用的就是xxx_write
close(fd); // 寫完之後關閉節點,此處最終調用的就是xxx_close
}