Linux字符驅動(一)
linux系統將設備分爲3類:字符設備、塊設備、網絡設備。
Linux系統框架如下圖:
字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據。字符設備按照字符流的方式被有序訪問。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制檯和LED設備等。
塊設備:是指可以從設備的任意位置讀取一定長度數據的設備。塊設備包括硬盤、磁盤、U盤和SD卡等。
這兩種類型的設備的根本區別在於它們是否可以被隨機訪問——換句話說就是,能否在訪問設備時隨意地從一個位置跳轉到另一個位置。舉個例子,鍵盤這種設備提供的就是一個數據流,當你敲入"fox" 這個字符串時,鍵盤驅動程序會按照和輸入完全相同的順序返回這個由三個字符組成的數據流。硬盤設備的驅動可能要求讀取磁盤上任意塊的內容,然後又轉去讀取別的塊的內容,而被讀取的塊在磁盤上位置不一定要連續,所以說硬盤可以被隨機訪問,而不是以流的方式被訪問,顯然它是一個塊設備。
每一個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序通過設備文件(或稱設備節點)來使用驅動程序操作字符設備和塊設備。
字符設備、字符設備驅動與用戶空間訪問該設備的程序三者之間的關係
如圖,在Linux內核中:
a – 使用cdev結構體來描述字符設備;
b – 通過其成員dev_t來定義設備號(分爲主、次設備號)以確定字符設備的唯一性;
c – 通過其成員file_operations來定義字符設備驅動提供給VFS的接口函數,如常見的open()、read()、write()等;
在Linux字符設備驅動中:
a – 模塊加載函數通過 register_chrdev_region( ) 或 alloc_chrdev_region( )來靜態或者動態獲取設備號;
b – 通過 cdev_init( ) 建立cdev與 file_operations之間的連接,通過 cdev_add( ) 向系統添加一個cdev以完成註冊;
c – 模塊卸載函數通過cdev_del( )來註銷cdev,通過 unregister_chrdev_region( )來釋放設備號;
用戶空間訪問該設備的程序:
a – 通過Linux系統調用,如open( )、read( )、write( ),來“調用”file_operations來定義字符設備驅動提供給VFS的接口函數;
設備號
內核用dev_t類型(<linux/types.h>)來保存設備編號,dev_t是一個32位的數,高12位表示主設備號,20爲表示次設備號。
在實際使用中,是通過<linux/kdev_t.h>中定義的宏來轉換格式。
(dev_t)–>主設備號、次設備號 | MAJOR(dev_t dev) MINOR(dev_t dev) |
---|---|
主設備號、次設備號–>(dev_t) | MKDEV(int major,int minor) |
註冊設備號的函數:
int register_chrdev_region(dev_t from, unsigned int count, char *name); //指定設備編號
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); //動態生成設備編號
void unregister_chrdev_region(dev_t from, unsigned int count); //釋放設備編號
指定設備號 | 自動分配設備號1 | 自動分配設備號2 |
---|---|---|
devno = MKDEV(major,minor); ret = register_chrdev_region(devno, 1, “hello”); cdev_init(&cdev,&hello_ops); ret = cdev_add(&cdev,devno,1); |
alloc_chrdev_region(&devno, minor, 1, “hello”); major = MAJOR(devno); cdev_init(&cdev,&hello_ops); ret = cdev_add(&cdev,devno,1); |
register_chrdev(major,“hello”,&hello_ops); |
創建設備文件:
使用mknod手工創建:
sudo mknod /dev/hello c major minor
chrdev01.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> // file_operations
#include <linux/cdev.h> // cdev_*
#include <linux/slab.h> // kfree
/////////////////////////////////////////////////////////////////
struct cdev *cdev;
dev_t devno;
/////////////////////////////////////////////////////////////////
int chrdev01_open (struct inode *inode, struct file *filp)
{
printk("chrdev01 open.\n");
return 0;
}
struct file_operations chrdev01_ops = {
.owner= THIS_MODULE,
.open = chrdev01_open,
};
/////////////////////////////////////////////////////////////////
static int __init chrdev01_init(void)
{
printk("chrdev01 init.\n");
devno = MKDEV(247, 1);
if (register_chrdev_region(devno, 1, "chrdev01")<0) {
//if (alloc_chrdev_region(&devno, 0, 1, "chrdev01")<0) {
printk("alloc_chrdev_region() failed!\n");
return -ENOMEM;
}
printk("chrdev01 major is:%d\n", MAJOR(devno));
cdev = cdev_alloc();
if (cdev==NULL) {
printk("cdev_alloc() failed!\n");
goto ERR_ALLOC;
}
cdev->owner = THIS_MODULE;
cdev_init(cdev, &chrdev01_ops);
if (cdev_add(cdev, devno, 1)<0) {
goto ERR_CDEV_ADD;
}
return 0;
ERR_CDEV_ADD:
ERR_ALLOC:
unregister_chrdev_region(devno, 1);
return -ENOMEM;
}
static void __exit chrdev01_exit(void)
{
cdev_del(cdev);
kfree(cdev);
unregister_chrdev_region(devno, 1);
printk("chrdev01 exit.\n");
}
module_init(chrdev01_init);
module_exit(chrdev01_exit);
MODULE_AUTHOR("Rbin.Yao");
MODULE_DESCRIPTION("A simple char device driver.");
MODULE_VERSION("V1.0");
MODULE_LICENSE("GPL");
Makefile:
obj-m := chrdev01.o
PWD := $(shell pwd)
KDIR := /lib/modules/$(shell uname -r)/build
all: chrdev01_test
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
@rm -f chrdev01_test
chrdev01_test:chrdev01_test.c
gcc $< -o $@
chrdev_test.c測試代碼:
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int fd;
fd = open("/dev/chrdev01", O_RDONLY);
if (fd<0) {
perror("open /dev/chrdev01 failed!");
} else {
printf("open /dev/chrdev01 ok!\n");
close(fd);
}
return 0;
}
參考文章
https://blog.csdn.net/zqixiao_09/article/details/50839042