環境
設備: Raspberry pi 5
OS : $ uname -a
參考資料
The Linux Kernel Module Programming Guide
事前作業
Raspberry Pi 5 Pinout
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
更新 Packages
安裝 Kernel Header
安裝工具包
重啟載入新 Kernel
$ls /dev
可以查看設備編號,透過給予這些編號將其與 kernel 連接。
顯示設備編號分別有 character dev 與 block dev
基本操作
當前有的 moduel
也可以直接從指定路徑尋找
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
載入 module
載卸 module
實作
Hellow World
kernel module 相比應用程序,必須告訴 kernel 我在哪,我該被載卸,因此必須設定 __init
、__exit
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
hello.c
Makefile
-C
到達此目錄後在編譯,編譯 hello.c
來生成 hello.o
,然後進一步生成 hello.ko
。
M=
使 Makefile 在試圖建立模塊前,回到你的 module 源碼目錄。
make
後
結果
另開一個終端來顯示輸出內容
dmesg
: 顯示 kernel 環境的訊息。
-W
: 監控模式
Read and Write
驅動程式類型 Block device 與 character device
- Block device
Block devices 可透過 /dev 中的 Filesystem nodes 來 Accessed。一個 Block 只能處理一到多個完整 Block 的 I/O 操作,大小為 512 bytes (或是更大的2的次方)。Linux 可以讓 User Space 讀取 Block device 如同操作File 一樣,而不用一次讀一整個 Block,在 User Space 中使用與 Char 基本上無差別。有差別的是在 Kernel & Driver 這一層完全不同。
- Character devices
Character devices 可以被當作 A stream of bytes 來被 Accessed,就像 File 一樣。因此 Char dirver 就要負責至少 open, close, read, and write 的 system calls 操作,有的還有提供 ioctl 的操作方式。常見的例子有 Text Console (/dev/console) and the Serial Ports (/dev/ttyS0),都是 Streaming 結構。Char devices 由 Filesystem nodes 來 accessed, such as /dev/tty1 and /dev/lp0。但跟一般 File 不同的是,一般 File 可以把指標往前往後來操作檔案,但是多數 Char device 只是 Data Channels,只能依序 Access。當然也有例外,而該例外多出現在 Frame Grabbers,就會像 Data Areas 一樣,可以在之中隨意前後操作,多用 mmap or lseek 來配合使用。
- Block 與 Character 的差異
Block devices 是以固定大小長度來傳送轉移資料,而 Character devices 是以不定長度的字元傳送資料,而其操作方式在 User Spcae 則大同小異。
取自 Driver (驅動) - 從零開始的開源地下城
read_write.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
static char buffer[255];
static size_t buffer_pointer = 0;
static dev_t my_dev_num;
static struct class *my_class;
static struct cdev my_dev;
#define DRIVER_NAME "dummydriver"
#define DRIVER_CLASS "MyModuleClass"
static ssize_t driver_read(struct file *file, char *usr_buffer, size_t count, loff_t *offs){
int to_copy, not_copied, delta;
to_copy = min(count, buffer_pointer);
not_copied = copy_to_user(usr_buffer, buffer, to_copy);
delta = to_copy - not_copied;
return delta;
}
static ssize_t driver_write(struct file *file, const char *usr_buffer, size_t count, loff_t *offs){
int to_copy, not_copied, delta;
to_copy = min(count, sizeof(buffer));
not_copied = copy_from_user(buffer, usr_buffer, to_copy);
buffer_pointer = to_copy;
delta = to_copy - not_copied;
return delta;
}
static int driver_open(struct inode *driver_file, struct file *instance){
printk("dev_num - open was called!\n");
return 0;
}
static int driver_close(struct inode *driver_file, struct file *instance){
printk("dev_num - close was called!\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = driver_open,
.release = driver_close,
.read = driver_read,
.write = driver_write
};
static int __init hello_init(void)
{
printk("Hello world!!!\n");
if(alloc_chrdev_region(&my_dev_num, 0, 1, DRIVER_NAME) < 0){
printk("device number cant be allocated!\n");
return -1;
}
printk("read_write - device number Major: %d, Minor: %d was registered!\n", my_dev_num >> 20, my_dev_num && 0xfffff);
if((my_class = class_create(DRIVER_CLASS)) == NULL){
printk("device class cant be created!\n");
goto ClassError;
}
if(device_create(my_class, NULL, my_dev_num, NULL, DRIVER_NAME) == NULL){
printk("cant create device file!\n");
goto FileError;
}
cdev_init(&my_dev, &fops);
if(cdev_add(&my_dev, my_dev_num, 1) == -1){
printk("register of device to kernel failed!\n");
goto AddError;
}
return 0;
AddError:
device_destroy(my_class, my_dev_num);
FileError:
class_destroy(my_class);
ClassError:
unregister_chrdev_region(my_dev_num, 1);
return -1;
}
static void __exit hello_exit(void)
{
cdev_del(&my_dev);
device_destroy(my_class, my_dev_num);
class_destroy(my_class);
unregister_chrdev_region(my_dev_num, 1);
printk("Goodbye, world\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
uaccess.h
用作 user 與 kernel 之間的數據傳輸,例如 copy_to_user
、copy_from_user
/include/linux/uaccess.h
下圖說明了讀取操作以及資料如何在用戶空間和驅動程式之間傳輸:
- when the driver has enough data available (starting with the OFFSET position) to accurately transfer the required size (SIZE) to the user.
- when a smaller amount is transferred than required.


設備編號
alloc_chrdev_region
: 向 linux kernel 申請設備號給定次設備號和名字,告訴linux要申請幾個設備號,讓linux直接幫你尋找主設備號
register_chrdev_region
: 自定義初始設備編號
unregister_chrdev_region
: 註銷設備號
註冊完會出現在 /proc/devices
、 sysfs
中,若註冊成功返回 0 ,失敗則為負值。
文件操作
/include/linux /fs.h
file_operations
結構體負責實現系統調用
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, struct io_comp_batch *,
unsigned int flags);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
void (*splice_eof)(struct file *file);
int (*setlease)(struct file *, int, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
int (*uring_cmd)(struct io_uring_cmd *ioucmd, unsigned int issue_flags);
int (*uring_cmd_iopoll)(struct io_uring_cmd *, struct io_comp_batch *,
unsigned int poll_flags);
} __randomize_layout;
當中 struct module *owner;
並非一個操作,他指向擁有這個結構的 module 指針。用來阻止 module 被卸載。
新字元設備註冊方式 cdev.h
/include/linux/cdev.h
使用 cdev
表示 char device
,然後使用 cdev_init()
初始化結構體,然後透過 cdev_add()
進行添加到linux kernel中,最後,註銷時使用 cdev_del()
。
錯誤處理
在註冊時發生任何錯誤,第一件事是決定 module 是否能夠無論如何繼續初始化他自己,常常,在一個註冊失敗後模組可以繼續操作, 如果需要可以功能降級. 在任何可能的時候, 你的模組應盡力向前, 並提供事情失敗後具備的能力。
若確定註冊失敗將導致整個 module 無法加載,使用 goto 可以省略大量複雜的程式。
goto 語句在失敗情況下使用, 在事情變壞之前只對先前已成功註冊的設施進行註銷。
注意: 按照註冊時得順序相反的註銷
Makefile
掛載 module
執行結果
GPIO driver
gpio_driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
static dev_t my_dev_num;
static struct class *my_class;
static struct cdev my_dev;
#define DRIVER_NAME "LED_gpio_driver"
#define DRIVER_CLASS "MyModuleClass"
#define GPIO_LED 575
#define GPIO_BUTTON 588
static ssize_t driver_read(struct file *file, char *usr_buffer, size_t count, loff_t *offs){
int to_copy, not_copied, delta;
char tmp[3] = " \n";
to_copy = min(count, sizeof(tmp));
printk("gpio 17 value: %d\n", gpio_get_value(GPIO_BUTTON));
tmp[0] = gpio_get_value(GPIO_BUTTON) + '0';
not_copied = copy_to_user(usr_buffer, &tmp, to_copy);
delta = to_copy - not_copied;
return delta;
}
static ssize_t driver_write(struct file *file, const char *usr_buffer, size_t count, loff_t *offs){
int to_copy, not_copied, delta;
char value;
to_copy = min(count, sizeof(value));
not_copied = copy_from_user(&value, usr_buffer, to_copy);
switch(value){
case '0':
gpio_set_value(GPIO_LED, 0);
break;
case '1':
gpio_set_value(GPIO_LED, 1);
break;
default:
printk("Invalid value!\n");
break;
}
delta = to_copy - not_copied;
return delta;
}
static int driver_open(struct inode *driver_file, struct file *instance){
printk("dev_num - open was called!\n");
return 0;
}
static int driver_close(struct inode *driver_file, struct file *instance){
printk("dev_num - close was called!\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = driver_open,
.release = driver_close,
.read = driver_read,
.write = driver_write
};
static int __init hello_init(void)
{
printk("Hello world!!!\n");
if(alloc_chrdev_region(&my_dev_num, 0, 1, DRIVER_NAME) < 0){
printk("device number cant be allocated!\n");
return -1;
}
printk("read_write - device number Major: %d, Minor: %d was registered!\n", my_dev_num >> 20, my_dev_num && 0xfffff);
if((my_class = class_create(DRIVER_CLASS)) == NULL){
printk("device class cant be created!\n");
goto ClassError;
}
if(device_create(my_class, NULL, my_dev_num, NULL, DRIVER_NAME) == NULL){
printk("cant create device file!\n");
goto FileError;
}
cdev_init(&my_dev, &fops);
if(cdev_add(&my_dev, my_dev_num, 1) == -1){
printk("register of device to kernel failed!\n");
goto AddError;
}
if(gpio_request(GPIO_LED, "rpi-gpio-4")){
printk("gpio 4 can't be allocated!\n");
goto AddError;
}
if(gpio_direction_output(GPIO_LED, 0)){
printk("can't set gpio 4 to output!\n");
goto Gpio4Error;
}
if(gpio_request(GPIO_BUTTON, "rpi-gpio-17")){
printk("gpio 17 can't be allocated!\n");
goto AddError;
}
if(gpio_direction_input(GPIO_BUTTON)){
printk("cant set gpio 17 to input!\n");
goto Gpio17Error;
}
return 0;
Gpio17Error:
gpio_free(GPIO_BUTTON);
Gpio4Error:
gpio_free(GPIO_LED);
AddError:
device_destroy(my_class, my_dev_num);
FileError:
class_destroy(my_class);
ClassError:
unregister_chrdev_region(my_dev_num, 1);
return -1;
}
static void __exit hello_exit(void)
{
gpio_set_value(GPIO_LED, 0);
gpio_free(GPIO_BUTTON);
gpio_free(GPIO_LED);
cdev_del(&my_dev);
device_destroy(my_class, my_dev_num);
class_destroy(my_class);
unregister_chrdev_region(my_dev_num, 1);
printk("Goodbye, world\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tsai Zhong-Han");
MODULE_DESCRIPTION("A simple gpio driver for setting a LED and reading a button");
GPIO 初始化:
以下為 GPIO 相關函式
gpio_request
: 根據 GPIO 編號註冊該 GPIO 並指定 label
gpio_direction_input
、 gpio_direction_output
: 設置 GPIO 輸出/輸入模式
Makefile
問題: 載入 module 時無法判別出 4 就是 GPIO4 ,因此查了 $ sudo cat /sys/kernel/debug/gpio
後才知道 GPIO4 對應到的是 gpio-575
錯誤結果
以下為查詢結果(省略部分內容,請自行查閱)
修正
輸出結果
head -n 1 /dev/LED_gpio_driver
命令嘗試從 /dev/LED_gpio_driver 設備檔中讀取第一行內容。
- LED 開關
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
- Button 開關
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
LCD Driver
lcd_driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/delay.h>
static dev_t my_dev_num;
static struct class *my_class;
static struct cdev my_dev;
#define DRIVER_NAME "lcd"
#define DRIVER_CLASS "MyModuleClass"
#define ENABLE_PIN 573
#define REGISTER_SELECT_PIN 574
#define LCD_DATA_PIN_0 575
#define LCD_DATA_PIN_1 588
#define LCD_DATA_PIN_2 598
#define LCD_DATA_PIN_3 593
#define LCD_DATA_PIN_4 581
#define LCD_DATA_PIN_5 580
#define LCD_DATA_PIN_6 582
#define LCD_DATA_PIN_7 576
static char lcd_buffer[17];
unsigned int gpios[] = {
ENABLE_PIN,
REGISTER_SELECT_PIN,
LCD_DATA_PIN_0,
LCD_DATA_PIN_1,
LCD_DATA_PIN_2,
LCD_DATA_PIN_3,
LCD_DATA_PIN_4,
LCD_DATA_PIN_5,
LCD_DATA_PIN_6,
LCD_DATA_PIN_7
};
#define ENABLE gpios[0]
#define REGISTER_SELECT gpios[1]
void lcd_enable(void){
gpio_set_value(gpios[0], 1);
msleep(20);
gpio_set_value(gpios[0], 0);
}
void lcd_send_byte(char data){
int i;
for(i = 0; i < 8; i++){
gpio_set_value(gpios[i+2], ((data) & (1<<i)) >> i);
}
lcd_enable();
msleep(20);
}
void lcd_command(uint8_t data){
gpio_set_value(REGISTER_SELECT, 0);
lcd_send_byte(data);
}
void lcd_data(uint8_t data){
gpio_set_value(REGISTER_SELECT, 1);
lcd_send_byte(data);
}
static ssize_t driver_write(struct file *file, const char *usr_buffer, size_t count, loff_t *offs){
int to_copy, not_copied, delta;
to_copy = min(count, sizeof(lcd_buffer));
not_copied = copy_from_user(lcd_buffer, usr_buffer, to_copy);
delta = to_copy - not_copied;
lcd_command(0x1);
for(int i = 0; i < to_copy; i++){
lcd_data(lcd_buffer[i]);
}
return delta;
}
static int driver_open(struct inode *driver_file, struct file *instance){
printk("dev_num - open was called!\n");
return 0;
}
static int driver_close(struct inode *driver_file, struct file *instance){
printk("dev_num - close was called!\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = driver_open,
.release = driver_close,
.write = driver_write
};
static int __init ModuleInit(void)
{
printk("Hello kernel!!!\n");
char *names[] = {"ENABLE_PIN", "REGISTER_SELECT", "DATA_PIN0",
"DATA_PIN1", "DATA_PIN2", "DATA_PIN3", "DATA_PIN4",
"DATA_PIN5", "DATA_PIN6", "DATA_PIN7"};
int i;
if(alloc_chrdev_region(&my_dev_num, 0, 1, DRIVER_NAME) < 0){
printk("device number cant be allocated!\n");
return -1;
}
printk("read_write - device number Major: %d, Minor: %d was registered!\n", my_dev_num >> 20, my_dev_num && 0xfffff);
if((my_class = class_create(DRIVER_CLASS)) == NULL){
printk("device class cant be created!\n");
goto ClassError;
}
if(device_create(my_class, NULL, my_dev_num, NULL, DRIVER_NAME) == NULL){
printk("cant create device file!\n");
goto FileError;
}
cdev_init(&my_dev, &fops);
if(cdev_add(&my_dev, my_dev_num, 1) == -1){
printk("register of device to kernel failed!\n");
goto AddError;
}
printk("initializing GPIOs\n");
for(i = 0; i < 10; i++){
if(gpio_request(gpios[i], names[i])){
printk("gpio %d can't be allocated!\n", gpios[i]);
goto GPIOInitError;
}
}
printk("setting GPIOs to output\n");
for(i = 0; i < 10; i++){
if(gpio_direction_output(gpios[i], 0)){
printk("gpio %d can't be set to output!\n", gpios[i]);
goto GPIODirectionError;
}
}
lcd_command(0x30);
lcd_command(0xf);
lcd_command(0x1);
char text[] = "Hello!";
for(i = 0; i < sizeof(text)-1; i++){
lcd_data(text[i]);
}
return 0;
GPIODirectionError:
i = 9;
GPIOInitError:
for(;i>=0; i--)
gpio_free(gpios[i]);
AddError:
device_destroy(my_class, my_dev_num);
FileError:
class_destroy(my_class);
ClassError:
unregister_chrdev_region(my_dev_num, 1);
return -1;
}
static void __exit ModuleExit(void)
{
lcd_command(0x1);
for(int i = 0; i < 10; i++){
gpio_set_value(gpios[i], 0);
gpio_free(gpios[i]);
}
cdev_del(&my_dev);
device_destroy(my_class, my_dev_num);
class_destroy(my_class);
unregister_chrdev_region(my_dev_num, 1);
printk("Goodbye, kernel\n");
}
module_init(ModuleInit);
module_exit(ModuleExit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tsai Zhong-Han");
MODULE_DESCRIPTION("A simple gpio driver for setting a LED and reading a button");

Command 對照表

Makefile
輸出結果
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
IOCtl
說明
參考教學 : Linux/Device_Driver/IOCTL
IOCtl 說明 :
IOCTL is referred to as Input and Output Control, which is used to talk to device drivers. This system call is available in most driver categories. The major use of this is in case of handling some specific operations of a device for which the kernel does not have a system call by default
處理 kernel 預設沒有系統呼叫的情境 => 讀寫設備 register 、修改 Serial port 的鮑率
步驟:
- 在 Driver 建立 IOCtl 命令
- 在 Driver 編寫 IOCtl 函式
- 在 user space 建立 IOCtl 命令
- 在 user space 使用 IOCtl 系統呼叫
定義 ioctl
指令
其中 __IOX
可以是:
IO
:不含參數的 ioctl
IOW
:具有寫入參數的 ioctl (copy_from_user
)
IOR
:具有讀取參數的 ioctl (copy_to_user
)
IOWR
:具有寫入和讀取參數的 ioctl
- Magic Number 是唯一的數字,用來區分所有 ioctl 呼叫。有時設置為主設備編號。
- 指派給 ioctl 的編號,用來區分命令編號
- Data 類型
範例:
定義 ioctl
函式
inode
:是正在處理的檔案的索引節點號
file
:是指向應用程式傳遞的檔案的檔案指標
cmd
:是從 user space 呼叫的 ioctl 指令
arg
:是從 user space 傳遞的參數
範例:
在 user space 使用 IOCTL
系統呼叫
引入標頭檔 #include <sys/ioctl.h>
file descriptor
:需要執行ioctl指令的開啟文件,一般是設備文件。
ioctl command
:為實現所需功能而實現的 ioctl 指令
arguments
:需要將參數傳遞給 ioctl 指令。
user space 範例:
其中 O_RDWR
開啟讀寫功能,還有像是 O_RDONLY
、O_WRONLY
等
程式碼
ioctl_ex.c
Makefile
test.c
實際結果

procfs
說明
/proc
並非存儲在硬碟上的實體文件系統,而是由內核在記憶體中動態生成的虛擬文件系統。這意味著其內容反映了系統的即時狀態,並且隨著系統的運行而變化。
/procfs
可以充當連接用戶空間和核心空間的橋樑。user space 程式可以使用 /proc
中的檔案來讀取 kernel 導出的資訊。
/proc
檔案系統中的每個條目都提供了一些來自核心的資訊,例如:
/proc/meminfo
給出了系統中正在使用的記憶體的詳細資訊,若要讀取此項目的資訊只需
更多項目=>
當我們想要調試核心模組時,/proc
檔案系統也非常有用。在偵錯時,我們可能想知道模組中各種變數的值或模組正在處理的資料。在這種情況下,我們可以為自己建立一個 /proc
條目,並轉儲我們想要在該條目中查看的任何資料。
/proc
也可以用於透過寫入核心的方式向核心傳遞數據,因此可以有兩種 /proc
- 僅從核心空間讀取資料的條目
- 從核心空間讀取資料以及將資料寫入核心空間的條目
程式碼
procfs_ex.c
創建在 /proc/*
底下的資料夾 API
- name : 創建
/proc
底下資料夾名稱
- parent : 指向該資料的指標
創建 proc 底下的 entry
- name : entry 名稱
- mode : 存取模式,
0666
=> 所有人、群組、其他人皆可讀寫, 開頭的 0 表示八進制
- parent 指向該 entry 的指標
- proc_fops : 將在其中建立 proc entry 的文件操作的結構
註冊該 proc entry filesystem
需要注意在這結構體只適用 Linux v5.6+ ,詳情可以看 lkmpg_the-procops-structure
Makefile
實際運行

sysfs
說明
sysfs 是針對特定裝置將系統資訊從核心空間匯出到使用者空間的常用方法
sysfs 與 procfs 差異在於,sysfs 專注於設備和驅動程序的屬性和狀態,procfs 提供系統運行時信息和執行緒資訊
kobject
- kobject 是核心(kernel)與 sysfs 文件系統之間的橋樑,定義在 <linux/kobject.h> 檔案中
- sysfs 是 Linux 中用來展示內核對象(如設備和驅動)的虛擬文件系統
- 一個 kobject 通常代表 kernel 中的某個物件,例如設備(device)或其他資源
那該如何在 /sys
底下建立子目錄,使用 kobject_create_and_add
函式
- name : 名稱
- parent : 說明參考如下
If you pass kernel_kobj
to the second argument, it will create the directory under /sys/kernel/
. If you pass firmware_kobj
to the second argument, it will create the directory under /sys/firmware/
. If you pass fs_kobj
to the second argument, it will create the directory under /sys/fs/
. If you pass NULL
to the second argument, it will create the directory under /sys/
.
若 kobject
結構體註冊失敗將回傳 NULL
This function creates a kobject structure dynamically and registers it with sysfs. If the kobject was not able to be created, NULL will be returned.
當結束此結構,使用 kobject_put
函式,該結構體將在不再被使用時被動態釋放
When you are finished with this structure, call kobject_put
and the structure will be dynamically freed when it is no longer being used
範例:
創建 sysfs file
在上面函式中我們建立 /sys
底下的子目錄,現在我們需要建立一個 sysfs 文件,該文件用於透過sysfs來實現 user space 與 kernel space 的互動。所以我們可以使用 sysfs 屬性來建立 sysfs 檔案
定義屬性: Kobj_attribute
其中 attribute 結構體定義名稱與權限
- attr : 定義屬性
- show : 指向在 sysfs 中讀取檔案時將呼叫的函數的指標,當我們向 sysfs 屬性寫入內容時,會呼叫store 函數
- store : 指向將檔案寫入 sysfs 時將被呼叫的函數的指標,當我們讀取 sysfs 屬性時,就會呼叫show 函數
建立 sysfs 檔案
- kobj : 正在為其建立的物件
- attr : 屬性描述
完成 sysfs 檔案後,您應該使用以下命令刪除該文件 sysfs_remove_file
範例:
程式碼
sysfs_test.c
當中使用 __ATTR
macro 以下為 linux/kobject.h
中的定義
/ include / linux / sysfs.h
在 kobject_create_and_add
函式的第二個參數中傳遞 kernel_kobj
,表示它將在 /sys/kernel/ 下建立目錄
If you pass kernel_kobj
to the second argument, it will create the directory under /sys/kernel/
. If you pass firmware_kobj
to the second argument, it will create the directory under /sys/firmware/
. If you pass fs_kobj
to the second argument, it will create the directory under /sys/fs
/. If you pass NULL
to the second argument, it will create the directory under /sys/
.
Makefile
實際結果

Device tree
參考:
說明
程式碼
實際結果
待更新
PWM Driver
查詢 Raspberry pi 5 上 pwm pin 對應在 kernel 上的編號
0.96 OLED Driver (i2c)
啟用 Raspberry pi5 的 i2c
選擇 Interfacing Options
找到 I2C,按下 Enable
確認是否有接正確並且顯示偵測到 i2c 設備
番外篇 : GPIO examples failed to compile on Kernel 6.10+ #285
更新 kernel 至 6.12.1
參考 : How to update your Raspberry Pi Kernel and install Kernel Headers
安裝 rpi-source 以取得核心標頭
此命令會取得核心原始碼並確保核心標頭與目前正在執行的核心正確對齊。
結果:
問題
lkmpg 的 issue GPIO examples failed to compile on Kernel 6.10+ #285
其中的問題點在於 gpio_request_array()
& gpio_free_array()
在 kernel 版本 6.10.rc1 中已被移除 Commit dbcedec
改成使用 gpio_request()
來啟用 GPIO 即可
在 unregister 使用 gpio_free()
來取代 gpio_free_array()
linux/gpio.h
修改後程式碼
編譯結果成功
dht11
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include <asm/errno.h>
#define DEVICE_NAME "dht11"
#define DEVICE_CNT 1
static uint16_t sensor_data[4] = { 0 };
struct dht11_dev {
dev_t dev_num;
int major_num, minor_num;
struct cdev cdev;
struct class *cls;
struct device *dev;
};
static struct dht11_dev dht11_device;
static struct gpio dht11[] = { { 575, GPIOF_OUT_INIT_HIGH, "Signal" } };
static int device_open(struct inode *inode, struct file *file)
{
return 0;
}
static int device_release(struct inode *inode, struct file *file)
{
return 0;
}
static int dht11_read_data(void)
{
int timeout;
uint8_t data[5] = { 0 };
uint8_t i, j;
gpio_set_value(dht11[0].gpio, 0);
mdelay(20);
gpio_set_value(dht11[0].gpio, 1);
udelay(30);
gpio_direction_input(dht11[0].gpio);
udelay(2);
pr_info("start success\n");
timeout = 500;
while (gpio_get_value(dht11[0].gpio) && timeout--)
udelay(1);
if (!timeout)
return -ETIMEDOUT;
timeout = 500;
while (!gpio_get_value(dht11[0].gpio) && timeout--)
udelay(1);
if (!timeout)
return -ETIMEDOUT;
timeout = 500;
while (gpio_get_value(dht11[0].gpio) && timeout--)
udelay(1);
if (!timeout)
return -ETIMEDOUT;
pr_info("dht11 responed success\n");
for (j = 0; j < 5; j++) {
uint8_t byte = 0;
for (i = 0; i < 8; i++) {
timeout = 500;
while (gpio_get_value(dht11[0].gpio) && timeout--)
udelay(1);
if (!timeout)
return -ETIMEDOUT;
timeout = 500;
while (!gpio_get_value(dht11[0].gpio) && timeout--)
udelay(1);
if (!timeout)
return -ETIMEDOUT;
udelay(40);
byte <<= 1;
if (gpio_get_value(dht11[0].gpio))
byte |= 0x01;
}
data[j] = byte;
}
if (data[4] != (uint8_t)(data[0] + data[1] + data[2] + data[3]))
return -EIO;
sensor_data[0] = data[0];
sensor_data[1] = data[1];
sensor_data[2] = data[2];
sensor_data[3] = data[3];
return 0;
}
static ssize_t device_read(struct file *filp, char __user *buf, size_t cnt,
loff_t *off)
{
int ret;
size_t to_copy = min(cnt, sizeof(sensor_data));
ret = dht11_read_data();
if (ret) {
gpio_direction_output(dht11[0].gpio, 1);
return ret;
}
gpio_direction_output(dht11[0].gpio, 1);
if (copy_to_user(buf, sensor_data, to_copy))
return -EFAULT;
return to_copy;
}
static struct file_operations fops = {
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
.owner = THIS_MODULE,
#endif
.read = device_read,
.open = device_open,
.release = device_release,
};
static int __init dht11_init(void)
{
int ret = 0;
if (dht11_device.major_num) {
dht11_device.dev_num =
MKDEV(dht11_device.major_num, dht11_device.minor_num);
ret = register_chrdev_region(dht11_device.dev_num, DEVICE_CNT,
DEVICE_NAME);
} else {
ret = alloc_chrdev_region(&dht11_device.dev_num, 0, DEVICE_CNT,
DEVICE_NAME);
}
if (ret < 0) {
pr_alert("Failed to register character device, error: %d\n", ret);
return ret;
}
pr_info("Major = %d, Minor = %d\n", MAJOR(dht11_device.dev_num),
MINOR(dht11_device.dev_num));
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
dht11_device.cdev.owner = THIS_MODULE;
#endif
cdev_init(&dht11_device.cdev, &fops);
ret = cdev_add(&dht11_device.cdev, dht11_device.dev_num, 1);
if (ret) {
pr_err("Failed to add the device to the system\n");
goto fail1;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
dht11_device.cls = class_create(DEVICE_NAME);
#else
dht11_device.cls = class_create(THIS_MODULE, DEVICE_NAME);
#endif
if (IS_ERR(dht11_device.cls)) {
pr_err("Failed to create class for device\n");
ret = PTR_ERR(dht11_device.cls);
goto fail2;
}
dht11_device.dev = device_create(dht11_device.cls, NULL,
dht11_device.dev_num, NULL, DEVICE_NAME);
if (IS_ERR(dht11_device.dev)) {
pr_err("Failed to create the device file\n");
ret = PTR_ERR(dht11_device.dev);
goto fail3;
}
pr_info("Device created on /dev/%s\n", DEVICE_NAME);
ret = gpio_request(dht11[0].gpio, dht11[0].label);
if (ret) {
pr_err("Unable to request GPIOs for dht11: %d\n", ret);
goto fail4;
}
ret = gpio_direction_output(dht11[0].gpio, dht11[0].flags);
if (ret) {
pr_err("Failed to set GPIO %d direction\n", dht11[0].gpio);
goto fail5;
}
return 0;
fail5:
gpio_free(dht11[0].gpio);
fail4:
device_destroy(dht11_device.cls, dht11_device.dev_num);
fail3:
class_destroy(dht11_device.cls);
fail2:
cdev_del(&dht11_device.cdev);
fail1:
unregister_chrdev_region(dht11_device.dev_num, DEVICE_CNT);
return ret;
}
static void __exit dht11_exit(void)
{
gpio_set_value(dht11[0].gpio, 0);
gpio_free(dht11[0].gpio);
device_destroy(dht11_device.cls, dht11_device.dev_num);
class_destroy(dht11_device.cls);
cdev_del(&dht11_device.cdev);
unregister_chrdev_region(dht11_device.dev_num, DEVICE_CNT);
}
module_init(dht11_init);
module_exit(dht11_exit);
MODULE_LICENSE("GPL");
pull request 可能會用到 git 知識
先將你新的修改加到暫存區 (stage):
變更上次 commit 可以用
這會把目前暫存的檔案變更,融合進「上一次 commit」,並讓你可以編輯該 commit 訊息。
如果只想替換內容但不改訊息,可加上 –no-edit。
如果這個分支已經 push 到遠端,要用 force push: