字符設備驅動

字符設備驅動

驅動分類:字符設備,塊設備,網絡接口設備。

什麼是字符設備:按字節來訪問的設備,驅動通常實現open,read,write等系統調用。

字符設備驅動模型

這裏寫圖片描述

字符設備程序設計

設備號

(1)設備號是什麼:字符設備通過字符設備文件來存取。輸入 ls -l輸出的第一列是‘c’,這就是字符設備文件的標識。還有逗號分隔的兩個數,他們分別是字符設備文件的主次設備號。
主設備號:用來標識與設備文件相連的驅動程序,用來反應設備是什麼類型。
次設備號:被驅動程序用來辨別操作的是哪個設備,用來區分同類型的設備。
描述:dev_t:實質是unsigned int 32位整數,其中高12位是主設備號,低20位是次設備號。
主設備號:MAJOR(dev_t dev)
次設備號:MINOR(dev_t dev)
(2)分配設備號:靜態申請和動態分配。
1. 靜態申請:使用函數register_chrdev_region註冊設備號,優點是簡單,缺點是一旦驅動被廣泛使用,這個隨機的主設備號可能會導致設備號衝突,導致驅動程序無法註冊。
函數原型:int register_chrdev_region(dev_t from, unsigned count, const char* name);
功能:申請使用從from開始的count個設備號(主設備號不變,次設備號增加)
參數解析:
from:希望申請使用的設備號
count:希望申請使用的設備號數目
name:設備名(體現在/proc/device)
2. 動態分配:使用函數alloc_chrdev_region分配設備號,優點是簡單,易於驅動推廣,缺點是無法在安裝驅動前創建設備文件(因爲在安裝前還沒有分配到設備號)
函數原型:int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
功能:請求內核動態分配count個設備號,且次設備號從baseminor開始。
參數解析:
dev:分配到的設備號
baseminor:起始的次設備號
count:需要分配的設備號數目
name:設備名
(3)註銷設備號:不論哪種方法分配設備號,不用的時候都應該釋放設備號。
函數原型:void unregister_chrdev_region(dev_t from, unsigned count);
功能:釋放從from開始的count個設備號。

創建設備文件:命令mknod手動創建

使用:mkmod filename type major minor

設備註冊

(1)描述:struct cdev
(2)註冊:3個步驟
1. 分配cdev
struct cdev的分配可使用cdev_alloc函數完成
使用:struct cdev* cdev_alloc(void)
2. 初始化cdev
struct cdev的初始化使用cdev_init函數完成
使用:void cdev_init(struct cdev* cdev, const struct file_operations* fops)
參數解析:
cdev:待初始化的cdev結構
fops:設備對應的操作函數
3. 添加cdev
struct cdev的註冊使用cdev_add函數完成
使用:int cdev_add(struct cdev* p, dev_t dev, unsigned count)
參數解析:
p:待添加到內核的字符設備驅動結構
dev:設備號
count:添加的設備個數

3種重要數據結構

struct file, struct inode, struct file_operations
(1)struct file:代表打開的文件。Linux系統中每一個打開的文件在內核空間都有一個關聯的struct file。他由內核在打開文件時創建,在文件關閉後釋放。
重要成員:loff_t f_pos 文件讀寫位置
(2)struct file_operations* f_op:一個函數指針的集合,定義可以在設備進行的操作。結構中的成員指向驅動中的函數,這些函數實現一個特別的操作,對於不支持的操作保留爲NULL
(3)struct inode:用來記錄文件的物理上的信息。和file結構不同,一個文件可以對應多個file結構,但是隻有一個inode結構。
重要成員:dev_t i_rdev 設備號

設備操作
open:設備文件的第一個操作

函數原型:int (open)(struct inode, struct file*)
並不要求驅動程序一定要實現這個操作。如果該項是NULL,設備的打開操作永遠成功的。
在大部分驅動程序中,open完成的工作是:初始化設備,標明次設備號。

release:設備文件關閉時調用這個操作

函數原型:void (release)(struct inode, struct file*)
與open類似,release也可以沒有。作用與open相反:關閉設備。

read:從設備中讀數據到用戶空間

函數原型:ssize_t(read)(struct file, char __user*, size_t, loff_t*);

write:向設備發數據,將數據傳遞給驅動程序

函數原型:ssize_t(write)(struct file, char __user*, size_t, loff_t*);
讀寫類似:ssize_t xxx_read(struct file* filp, char __user* buff, size_t count, loff_t* offp);
ssize_t xxx_write(struct file* filp, char __user* buff, size_t count, loff_t* offp);
參數解析:
1. filp:文件指針
2. count:請求傳輸的數據量
3. buff:用戶空間指針,指向數據緩存。不能被內核代碼直接調用,因爲用戶空間指針在內核空間可能根本是無效的,沒有地址映射。內核提供了專門的函數用於訪問用戶空間指針:
int copy_from_user(void* to, const void __user* from, int n);
int copy_to_user(void __user* to, const void* from, int n);
4. offp:文件當前的訪問位置

poll:對應select系統調用

函數原型:unsigned int(poll)(struct file, struct poll_table_struct*)

ioctl:控制設備

函數原型:int (ioctl)(struct inode, struct file*, unsigned int, unsigned long)

mmap:將設備映射到進程虛擬地址空間中

函數原型:int (mmap)(struct file, struct vm_area_struct*)

llseek:修改文件當前位置,並將新位置作爲返回值

函數原型:off_t (llseek)(struct file, loff_t, int)

cdev_del:字符設備註銷

函數原型:int cdev_del(struct cdev* p)
參數解析:p是要註銷的字符設備

範例設計:簡單字符驅動程序

開發一個基本的字符設備:建立一個名爲GlobalChar的虛擬設備,設備內部只有一個全局變量供用戶操作。
設備提供功能:
1. 讀函數讀取設備內部全局變量的值返回給用戶
2. 寫函數把用戶設定的值寫入全局變量

步驟如下:

編寫代碼:GlobalCharDev.c
#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>
#include<cdev.h>
#include<linux/fs.h>
#include<linux/kdev_t.h>
#include<asm/uaccess.h>
#include<linux/device.h>
#define DEV_NAME "GlobalChar"

static ssize_t GlobalRead(struct file *, char *, size_t, loff_t *);
static ssize_t GlobalWrite(struct file *, const char *, size_t, loff_t *);

static int char_major = 0;
static int GlobalData = 0;

static const struct file_operations globalchar_fops =
{
     .read = GlobalRead,
     .write = GlobalWrite
};

static int __init GlobalChar_init(void)
{
     int ret;
     ret = register_chrdev(char_major, DEV_NAME, &globalchar_fops);
     if(ret < 0)
     {
          printk(KERN_ALERT "GlobalChar register failed!\n");
     }
     else
     {
          printk(KERN_ALERT "GlobalChar register sucess!\n");
          char_major = ret;
          printk(KERN_ALERT "major = %d\n", char_major);
     }
     return ret;
}

static void __exit GlobalChar_exit(void)
{
     unregister_chrdev(char_major, DEV_NAME);
     return;
}

static ssize_t GlobalRead(struct file *filp, char *buf, size_t len, loff_t *off)
{
     if(copy_to_user(buf, &GlobalData, sizeof(int)))
     {
          return -EFAULT;
     }
     return sizeof(int);
}

static ssize_t GlobalWrite(struct file *filp, const char *buf, size_t len, loff_t *off)
{
     if(copy_from_user(&GlobalData, buf, sizeof(int)))
     {
          return -EFAULT;
     }
     return sizeof(int);
}

module_init(GlobalChar _init);
module_exit(GlobalChar _exit);
編寫Makefile
obj-m := GlobalCharDev.o
KDIR := /lib/modules/$(shell uname -r)/bulid
SRCPWD := $(shell pwd)
all:
     make -C $(KDIR) M=$(SRCPWD) modules
編譯並加載內核模塊
查看內核分配的主設備號:dmesg | tail -n 10和cat /proc/devices | grep GlobalChar
使用mknod命令創建一個設備文件:sudo mknod -m 666 /dev/GlobalChar c 249 0

到此爲止,已經成功添加了一個字符設備到內核,下面是測試驅動程序能否正常工作。

測試代碼:GlobalCharTest.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define DEV_NAME "/dev/GlobalChar"

int main()
{
     int fd, num;
     fd = open(DEV_NAME, O_RDWR, S_IRUSR | S_IWUSR);
     if(fd < 0)
     {
          printf("open device fail!\n");
          return -1;
     }
     read(fd, &num, sizeof(int));
     printf("the GlobalChar is %d\n", num);
     printf("please input a number written to GlobalChar: ");
     scanf("%d", &num);
     write(fd, &num, sizeof(int));
      read(fd, &num, sizeof(int));
      printf("the GlobalChar is %d\n", num);
     close(fd);
     return 0;
}
發佈了32 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章