安裝wordpress報(bào)404錯(cuò)誤網(wǎng)站seo專(zhuān)員
Linux 字符設(shè)備驅(qū)動(dòng)開(kāi)發(fā)是內(nèi)核模塊開(kāi)發(fā)中的一個(gè)重要部分,主要用于處理字節(jié)流數(shù)據(jù)設(shè)備(如串口、鍵盤(pán)、鼠標(biāo)等)。字符設(shè)備驅(qū)動(dòng)的核心任務(wù)是定義如何與用戶(hù)空間程序交互,通常通過(guò)一組文件操作函數(shù)進(jìn)行。這些函數(shù)會(huì)映射到 open
、read
、write
等系統(tǒng)調(diào)用。
下面將詳細(xì)介紹字符設(shè)備驅(qū)動(dòng)開(kāi)發(fā)的步驟,包括編寫(xiě)、注冊(cè)、操作函數(shù)實(shí)現(xiàn)、測(cè)試等。
1. 字符設(shè)備驅(qū)動(dòng)開(kāi)發(fā)流程
步驟 1: 創(chuàng)建一個(gè)內(nèi)核模塊
字符設(shè)備驅(qū)動(dòng)是作為內(nèi)核模塊加載的,可以動(dòng)態(tài)加載到 Linux 內(nèi)核中。我們從編寫(xiě)一個(gè)簡(jiǎn)單的字符設(shè)備驅(qū)動(dòng)模塊開(kāi)始。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h> // 文件系統(tǒng)支持
#include <linux/cdev.h> // 字符設(shè)備支持
#include <linux/uaccess.h> // 用戶(hù)空間訪(fǎng)問(wèn)支持#define DEVICE_NAME "mychardev"
#define BUFFER_SIZE 1024static int major; // 主設(shè)備號(hào)
static char device_buffer[BUFFER_SIZE]; // 設(shè)備數(shù)據(jù)緩沖區(qū)
static struct cdev my_cdev; // 字符設(shè)備結(jié)構(gòu)體
static dev_t dev_num; // 設(shè)備號(hào)// 文件操作函數(shù)
static int device_open(struct inode *inode, struct file *file) {printk(KERN_INFO "Device opened\n");return 0;
}static int device_release(struct inode *inode, struct file *file) {printk(KERN_INFO "Device closed\n");return 0;
}static ssize_t device_read(struct file *file, char __user *buffer, size_t len, loff_t *offset) {size_t bytes_read = len < BUFFER_SIZE ? len : BUFFER_SIZE;if (copy_to_user(buffer, device_buffer, bytes_read)) {return -EFAULT;}printk(KERN_INFO "Read %zu bytes from device\n", bytes_read);return bytes_read;
}static ssize_t device_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset) {size_t bytes_write = len < BUFFER_SIZE ? len : BUFFER_SIZE;if (copy_from_user(device_buffer, buffer, bytes_write)) {return -EFAULT;}printk(KERN_INFO "Wrote %zu bytes to device\n", bytes_write);return bytes_write;
}// 文件操作函數(shù)結(jié)構(gòu)體
static struct file_operations fops = {.owner = THIS_MODULE,.open = device_open,.release = device_release,.read = device_read,.write = device_write,
};// 模塊加載時(shí)的初始化函數(shù)
static int __init mychardev_init(void) {// 動(dòng)態(tài)分配主設(shè)備號(hào)和次設(shè)備號(hào)alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);major = MAJOR(dev_num);printk(KERN_INFO "Registered with major number %d\n", major);// 初始化 cdev 結(jié)構(gòu)體并添加到系統(tǒng)cdev_init(&my_cdev, &fops);cdev_add(&my_cdev, dev_num, 1);return 0;
}// 模塊卸載時(shí)的清理函數(shù)
static void __exit mychardev_exit(void) {// 刪除 cdevcdev_del(&my_cdev);// 釋放設(shè)備號(hào)unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "Device unregistered\n");
}module_init(mychardev_init);
module_exit(mychardev_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example Author");
MODULE_DESCRIPTION("A simple character device driver");
步驟 2: 編寫(xiě) Makefile
為了編譯驅(qū)動(dòng)模塊,需要編寫(xiě)一個(gè) Makefile
來(lái)調(diào)用內(nèi)核構(gòu)建系統(tǒng)。
obj-m += mychardev.oall:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
步驟 3: 編譯驅(qū)動(dòng)
編譯驅(qū)動(dòng):
make
生成的模塊文件為 mychardev.ko
。
步驟 4: 加載和卸載驅(qū)動(dòng)
加載字符設(shè)備驅(qū)動(dòng)模塊:
sudo insmod mychardev.ko
檢查是否成功加載驅(qū)動(dòng):
dmesg | tail
卸載模塊:
sudo rmmod mychardev
步驟 5: 創(chuàng)建設(shè)備文件
Linux 使用設(shè)備文件與用戶(hù)空間通信。驅(qū)動(dòng)模塊加載時(shí),需要為字符設(shè)備創(chuàng)建設(shè)備文件。
首先通過(guò) /proc/devices
查看分配的主設(shè)備號(hào):
cat /proc/devices | grep mychardev
使用 mknod
創(chuàng)建設(shè)備文件:
sudo mknod /dev/mychardev c <major_number> 0
sudo chmod 666 /dev/mychardev
2. 文件操作函數(shù)實(shí)現(xiàn)
1. open
和 release
函數(shù)
這些函數(shù)會(huì)在打開(kāi)和關(guān)閉設(shè)備文件時(shí)被調(diào)用。它們通常用于初始化設(shè)備或者釋放設(shè)備資源。
open
:每次用戶(hù)空間程序通過(guò)open()
調(diào)用打開(kāi)設(shè)備文件時(shí)調(diào)用,通常用于設(shè)備初始化。release
:每次關(guān)閉設(shè)備文件時(shí)調(diào)用,用于釋放資源。
static int device_open(struct inode *inode, struct file *file) {printk(KERN_INFO "Device opened\n");return 0;
}static int device_release(struct inode *inode, struct file *file) {printk(KERN_INFO "Device closed\n");return 0;
}
2. read
和 write
函數(shù)
這些函數(shù)分別實(shí)現(xiàn)用戶(hù)空間程序?qū)υO(shè)備的讀取和寫(xiě)入操作。
read
:讀取設(shè)備的數(shù)據(jù),用戶(hù)空間調(diào)用read()
時(shí)觸發(fā)。write
:將用戶(hù)空間的數(shù)據(jù)寫(xiě)入設(shè)備,用戶(hù)空間調(diào)用write()
時(shí)觸發(fā)。
static ssize_t device_read(struct file *file, char __user *buffer, size_t len, loff_t *offset) {size_t bytes_read = len < BUFFER_SIZE ? len : BUFFER_SIZE;if (copy_to_user(buffer, device_buffer, bytes_read)) {return -EFAULT;}printk(KERN_INFO "Read %zu bytes from device\n", bytes_read);return bytes_read;
}static ssize_t device_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset) {size_t bytes_write = len < BUFFER_SIZE ? len : BUFFER_SIZE;if (copy_from_user(device_buffer, buffer, bytes_write)) {return -EFAULT;}printk(KERN_INFO "Wrote %zu bytes to device\n", bytes_write);return bytes_write;
}
3. 設(shè)備號(hào)分配與 cdev
結(jié)構(gòu)
字符設(shè)備必須注冊(cè)到內(nèi)核中,以使內(nèi)核能夠通過(guò)設(shè)備號(hào)找到驅(qū)動(dòng)程序。主設(shè)備號(hào)用于標(biāo)識(shí)驅(qū)動(dòng)程序,次設(shè)備號(hào)用于標(biāo)識(shí)設(shè)備實(shí)例。
- 使用
alloc_chrdev_region
動(dòng)態(tài)分配設(shè)備號(hào)。 - 使用
cdev_init
和cdev_add
將字符設(shè)備添加到內(nèi)核中。 - 使用
cdev_del
刪除設(shè)備。
alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
cdev_init(&my_cdev, &fops);
cdev_add(&my_cdev, dev_num, 1);
4. 測(cè)試驅(qū)動(dòng)
編寫(xiě)一個(gè)簡(jiǎn)單的用戶(hù)空間測(cè)試程序來(lái)與字符設(shè)備驅(qū)動(dòng)交互。
用戶(hù)空間測(cè)試程序
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>#define DEVICE_PATH "/dev/mychardev"
#define BUFFER_SIZE 1024int main() {int fd;char write_buffer[BUFFER_SIZE] = "Hello, Kernel!";char read_buffer[BUFFER_SIZE];// 打開(kāi)設(shè)備文件fd = open(DEVICE_PATH, O_RDWR);if (fd < 0) {perror("Failed to open the device");return EXIT_FAILURE;}// 寫(xiě)數(shù)據(jù)到設(shè)備printf("Writing to device: %s\n", write_buffer);if (write(fd, write_buffer, strlen(write_buffer)) < 0) {perror("Failed to write to the device");close(fd);return EXIT_FAILURE;}// 清空讀取緩沖區(qū)memset(read_buffer, 0, sizeof(read_buffer));// 從設(shè)備讀取數(shù)據(jù)if (read(fd, read_buffer, sizeof(read_buffer)) < 0) {perror("Failed to read from the device");close(fd);return EXIT_FAILURE;}// 輸出從設(shè)備讀取到的數(shù)據(jù)printf("Read from device: %s\n", read_buffer);// 關(guān)閉設(shè)備文件close(fd);return EXIT_SUCCESS;
}
當(dāng)成功編寫(xiě)、加載并測(cè)試字符設(shè)備驅(qū)動(dòng)時(shí),用戶(hù)空間程序會(huì)通過(guò)標(biāo)準(zhǔn)輸出顯示與驅(qū)動(dòng)交互的結(jié)果。下面是驅(qū)動(dòng)程序的純輸出示例,假設(shè)測(cè)試程序成功與字符設(shè)備驅(qū)動(dòng)交互:
用戶(hù)空間測(cè)試程序輸出
Writing to device: Hello, Kernel!
Read from device: Hello, Kernel!
內(nèi)核日志 (dmesg
) 輸出
[ 123.456789] Registered with major number 240
[ 123.456890] Device opened
[ 123.457123] Wrote 13 bytes to device
[ 123.457345] Read 13 bytes from device
[ 123.457567] Device closed
輸出解釋
-
用戶(hù)空間測(cè)試程序輸出:
- 測(cè)試程序?qū)⒆址?
"Hello, Kernel!"
寫(xiě)入字符設(shè)備,并隨后讀取回相同的字符串。 Writing to device: Hello, Kernel!
表示程序已成功向設(shè)備寫(xiě)入數(shù)據(jù)。Read from device: Hello, Kernel!
表示程序已成功從設(shè)備讀取數(shù)據(jù)。
- 測(cè)試程序?qū)⒆址?
-
內(nèi)核日志 (
dmesg
) 輸出:Registered with major number 240
表示字符設(shè)備驅(qū)動(dòng)成功注冊(cè),并分配了主設(shè)備號(hào) 240。Device opened
表示設(shè)備文件被打開(kāi),說(shuō)明用戶(hù)空間程序調(diào)用了open()
系統(tǒng)調(diào)用。Wrote 13 bytes to device
表示用戶(hù)空間程序?qū)懭肓?13 字節(jié)的數(shù)據(jù)到設(shè)備。Read 13 bytes from device
表示用戶(hù)空間程序從設(shè)備讀取了 13 字節(jié)的數(shù)據(jù)。Device closed
表示設(shè)備文件被關(guān)閉,說(shuō)明用戶(hù)空間程序調(diào)用了close()
系統(tǒng)調(diào)用。
這些輸出可以幫助你確認(rèn)驅(qū)動(dòng)程序的各個(gè)操作函數(shù)被正確調(diào)用,并且用戶(hù)空間程序與字符設(shè)備的交互是成功的。
5. 總結(jié)
通過(guò)上述步驟可以開(kāi)發(fā)一個(gè)簡(jiǎn)單的字符設(shè)備驅(qū)動(dòng)程序。字符設(shè)備驅(qū)動(dòng)的核心是通過(guò) file _operations
結(jié)構(gòu)體實(shí)現(xiàn)的操作函數(shù),包括 open
、read
、write
和 release
等。在用戶(hù)空間編寫(xiě)簡(jiǎn)單的測(cè)試程序,使用 open()
、read()
、write()
系統(tǒng)調(diào)用與字符設(shè)備進(jìn)行交互,從而驗(yàn)證驅(qū)動(dòng)程序的正確性。