Linux
Block Device
Character Device
Ref: LDD3
在我們的裝置上,不論是電腦、手機甚至是網通設備這樣無法與人直接互動的設備等,皆需要 Driver 的存在,常見的裝置有"藍芽"、"網路卡"或是"鍵盤滑鼠"…等,然而這些裝置如果沒有像 Driver 這樣偏向硬體底層的軟體,那使用者將會需要耗費需多資源來進行程式的開發。
上圖為 Linux Kernel 的拆解分視圖,在一部嵌入式裝置上會提供許多功能,例如 行程管理 (Process management)、記憶體管理 (Memory management)、檔案系統格式驅動 (Filesystems)、裝置設備控制 (Device control)及網路驅動控制 (Networking)等。大致上分為以下幾種Block devices、Character devices,在後面將會詳細介紹這幾種驅動型態。
在本章節將介紹如何建立一個驅動程式來讓 User Space 程式來進行使用,並且完成 Block Device 及 Character 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 可以被當作 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 devices 是以固定大小長度來傳送轉移資料,而 Character devices 是以不定長度的字元傳送資料,而其操作方式在 User Spcae 則大同小異。
那麼首先就是來建立一個空的 Block devices 吧,而我們要如何建立一支 Driver 呢?
那就必須要有他的主程式,在此我們命名為my_block.c
,而內容如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/blkdev.h>
MODULE_LICENSE("Dual BSD/GPL");
#define MY_BLOCK_MAJOR 240
#define MY_BLKDEV_NAME "my_block"
static __init int my_block_init(void)
{
int status;
status = register_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME);
if (status < 0) {
printk(KERN_ERR "unable to register mybdev block device\n");
return -EBUSY;
}
printk(KERN_INFO "Hello kernel\n");
return 0;
}
static void __exit my_block_exit(void)
{
printk(KERN_INFO "Goodbye\n");
unregister_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME);
}
module_init(my_block_init);
module_exit(my_block_exit);
而每一隻 Driver 都有共通的函式,init
及exit
,分別式掛載時用與卸載時用,然而 Block driver 需要向 Kernel 註冊自己,需要使用到 <linux/blkdev.h>
中的 register_blkdev
及 unregister_blkdev
來進行註冊與註銷的動作。
而每個 Block Device 都必須有個 Major Number ,該編號在 Kernel 中可使用的範圍定義於 linux/kdev_t.h
當中。掛載的方式為 insmod <DRIVER_NAME.ko>
,掛在上去後可用 sed -n '/^Block/, /^$/ { /^$/ !p }' /proc/devices
來查看裝置是否存在。
相關的 Source Code 請參考 Github 04-driver
在前一段中,我們說明了 Block Devices Driver 的建立以及 Driver 基本所需的函式,而在此將說明如何建立一個簡單的 Character Devices Driver,在此我們命名為my_char.c
,而內容如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/ioctl.h>
MODULE_LICENSE("Dual BSD/GPL");
#define MY_CDEB_MAJOR 42
#define MY_CDEV_NAME "my_char"
#define MY_MAX_MINORS 5
struct my_device_data {
struct cdev cdev;
};
struct my_device_data devs[MY_MAX_MINORS];
const int arr[5];
static int my_open(struct inode *inode, struct file *file)
{
return 0;
}
static int my_read(struct file *file, char __user *user_buffer,
size_t size, loff_t *offset)
{
return 0;
}
static int my_write(struct file *file, const char __user *user_buffer,
size_t size, loff_t * offset)
{
return 0;
}
static int my_release(struct inode *inode , struct file *filp)
{
return 0;
}
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return 0;
}
const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_release,
.unlocked_ioctl = my_ioctl
};
static __init int my_char_init(void)
{
int i, err;
err = register_chrdev_region(MKDEV(MY_CDEB_MAJOR, 0), MY_MAX_MINORS,
MY_CDEV_NAME);
if (err != 0) {
/* report error */
return err;
}
for(i = 0; i < MY_MAX_MINORS; i++) {
/* initialize devs[i] fields */
cdev_init(&devs[i].cdev, &my_fops);
cdev_add(&devs[i].cdev, MKDEV(MY_CDEB_MAJOR, i), 1);
}
printk(KERN_INFO "Hello kernel\n");
return 0;
}
static void __exit my_char_exit(void)
{
int i;
printk(KERN_INFO "Goodbye\n");
for(i = 0; i < MY_MAX_MINORS; i++) {
/* release devs[i] fields */
cdev_del(&devs[i].cdev);
}
unregister_chrdev_region(MKDEV(MY_CDEB_MAJOR, 0), MY_MAX_MINORS);
}
module_init(my_char_init);
module_exit(my_char_exit);
為了方便說明以下簡稱Block
與Char
,而與 Block 不同的是, Char 具有 read, write 等功能,使得使用者可以透過 Driver 來對 Devices 進行 Register 操作,進而做到 User Space 使用通用 Interface 即可使用多種多樣化的 Devices 。
可透過 sed -n '/^Character/, /^$/ { /^$/ !p }' /proc/devices
來查看裝置是否存在。
相關的 Source Code 請參考 Github 04-driver
在此我們將特別針對兩個常用函式進行說明,首先是 read()
:
static int my_read(struct file *file, char __user *user_buffer,
size_t size, loff_t *offset)
{
return 0;
}
此函數是使用者透過開啟 file descriptor
或是稱 fd
時,進行 read 操作時會調用之函式,而第一個引數 sturct file *file
為該 fd
資訊結構體,而第二個引數 const char __user *user_buffer
為該次 read 操作時可使用的 User Space 變數指標,第三個引數 size_t size
為欲讀取的資料筆數,最後一個引數 loff_t *offset
為該 fd
對於基準點的偏移量。
static int my_write(struct file *file, const char __user *user_buffer,
size_t size, loff_t * offset)
{
return 0;
}
此函數是使用者透過開啟 file descriptor
或是稱 fd
時,進行 write 操作時會調用之函式,而第一個引數 sturct file *file
為該 fd
資訊結構體,而第二個引數 const char __user *user_buffer
為該次 write 操作時可使用的 User Space 變數指標,第三個引數 size_t size
為欲寫入的資料筆數,最後一個引數 loff_t *offset
為該 fd
對於基準點的偏移量。
read()
及 write()
來操作 Driver 內的 arr
變數,也就是,每開啟一個 fd
就可以操作對應的 arr
變數 address,舉例來說,第一個 fd 可以操作 arr[0]
,第二個 fd 可以操作 arr[1]
以此類推。
測試程式位於此
測試方法為當 QEMU 啟動後,進入/home/pi/test
資料夾,並執行make check
即可Hints: 不可以讓 fd 操作不屬於該 fd 的 Register。
read
及 write
等 System Call 時,它的程式流程,並紀錄於筆記當中。