1. 程式人生 > >linux2.6標準字元裝置驅動模型(手動註冊)

linux2.6標準字元裝置驅動模型(手動註冊)

linux2.6標準字元裝置核心結構

//雖然linux26有核心結構體,但在程式碼中沒有具體操作,他們的呼叫在相關注冊函式中對其有操作
struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};

關鍵成員介紹

  • ops
    檔案操作集合
  • dev
    32位裝置號,包含主裝置號和次裝置號
  • count
    佔用連續的次裝置號數量,從dev中的次裝置號開始
  • 其餘成員與系統呼叫相關,使用者不用配置

裝置號

linux2.6以後用dev_t 32位整數表示裝置號,實質是u32型別,其中高12位是主裝置號,低20位是次裝置號。
主裝置號:dev_t高12位,2^12=4K,(10是給雜項裝置使用的)
早期字元裝置範圍:0~255 Linux2.6是0~4095
次裝置號:dev_t低20位,2^20=1M
早期字元裝置範圍:0~255(主裝置號一旦註冊,下屬次裝置號全部被註冊), Linux2.6是0~1M-1
合成裝置號:
MKDEV(ma,mi);ma:主裝置號;mi:次裝置號
分解裝置號:
MAJOR(dev)


MINOR(dev)
核心中定義

#define MINORBITS   20
#define MINORMASK   ((1U << MINORBITS) - 1)
#define MAJOR(dev)  ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)  ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

特徵

  • 安裝後不會在/dev目錄下建立裝置檔案節點,需要手動mknod
    建立
  • 呼叫一個cdev_add註冊後,指定數量的次裝置號將被佔用,數量自己制定,一個主裝置號可以被cdev_add註冊多次。
  • 裝置號使用前需要提前申請,register_chrdev_region靜態分配,或alloc_chrdev_region動態分配裝置號函式申請。

裝置號申請/登出函式

靜態裝置號申請函式

//靜態申請裝置號
int register_chrdev_region(dev_t from, unsigned count, const char *name)
from :指定的裝置號   250  1
count :裝置個數   5         250 1   250 2  250 3  250 4   250 5  
name: 裝置名  
返回值:成功返回0,失敗返回負數錯誤碼
  • 標頭檔案
    #include<linux/fs.h>
  • 功能
    註冊一個裝置號範圍
  • 引數
  • [ ] from起始裝置號(主、次)
  • [ ] count連續的次裝置號數量
  • [ ] name裝置名,不需要和/dev的裝置名相同
  • [ ] 其餘引數由核心使用,使用者不直接操作
  • 返回值
    成功:返回0, 失敗:返回負數

動態裝置號註冊函式

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                                                    const char *name)

dev: 接收動態分配的一系列裝置號的第一個裝置號
baseminor:指定一系列裝置號的第一個裝置號的次裝置號
count :裝置個數
name :裝置名

返回值:成功0,失敗為負數
  • 標頭檔案
    #include<linux/fs.h>
  • 功能
    註冊一個裝置號的範圍
  • 引數
  • [ ] *dev:存放分配到的第一個裝置(包含主次裝置號)
  • [ ] baseminor:要分配的起始次裝置號
  • [ ] count連續次裝置號的數量
  • [ ] name裝置名,不需要和/dev的裝置名相同
  • 返回值
    成功:返回0 失敗:返回負數

裝置號釋放函式

//釋放裝置號
void unregister_chrdev_region(dev_t from, unsigned count)
from: 第一個裝置號
count:裝置個數
  • 標頭檔案
    #include<linux/fs.h>
  • 功能
    登出一個裝置號範圍
  • 引數
  • [ ] from:起始裝置號(主、次)(包含主次裝置號)
  • [ ] count:連續的次裝置號數量
  • 返回值

核心結構分配函式

struct cdev *cdev_alloc(void); //分配儲存區
功能:在堆空間中分配一個核心結構體,注意不使用時用kfree函式釋放,
引數:
返回值:返回分配到struct cdev的結構首地址

說明:用完記得釋放,否則會造成記憶體洩漏

第一種方式:
struct cdev k;//定義變數,定以後已經有了struct cdev解構記憶體空間
第二種方式:
struct cdev *p://這種寫法只是定義了指標,但是沒有分配struct cdev記憶體空間,p=struct_cdev();//寫在模組載入函式中

核心結構struct cdev初始化函式

初始化函式

void cdev_init(struct cdev *xxx, const struct file_operations *fops); //初始化
- 標頭檔案
#include<linux/cdev.h>
- 功能
初始化核心結構,具體的做法是清零核心結構,初始化核心結構體的list,kobj,ops成員
- 引數
- [ ] cdev:需要初始化的核心結構體
- [ ] fops:檔案操作集合函式結構體
- 返回值

說明:寫這種驅動模型的時候,不需要定義struct cdev結構體初始化,因為在呼叫cdev_init結構體會初始化把它請0,定義時候的初始無效

真正的設備註冊/登出函式

//註冊驅動
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
p: 核心結構體
dev:裝置號
count:裝置個數

返回值:成功返回0,失敗返回負數錯誤碼
  • 標頭檔案
    #include<linux/cdev.h>
  • 功能
    註冊一個cdev結構
  • 引數
  • [ ] p:已經初始化的核心結構體指標
  • [ ] dev:起始裝置號(包含主次裝置號在內)
  • [ ] count:連續的次裝置號數量
  • 返回值
    成功:返回0 失敗:返回負數
//登出函式
void cdev_del(struct cdev *p);
p:核心結構體
  • 標頭檔案
    #include<linux/cdev.h>
  • 功能
    登出一個cdev結構
  • 引數
    p 前面註冊的struct cdev結構指標
  • 返回

linux2.6標準字元裝置驅動程式設計步驟

回顧雜項裝置
定義雜項裝置核心結構體,填充核心結構體,註冊核心結構。這個過程中,使用倒推法則,實現要什麼給什麼。
linux2.6模型方法相同。

模組的初始化函式

  • 使用cdev_alloc(void)分配cdev空間
  • 申請裝置號:靜態或者動態申請
//靜態申請
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//動態申請
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                                                    const char *name)
  • 初始化cdev結構
void cdev_init(struct cdev *xxx, const struct file_operations *fops); //初始化
  • 註冊已初始化好的cdev結構
//註冊驅動
int cdev_add(struct cdev *p, dev_t dev, unsigned count);

模組解除安裝函式做的事情和載入的函式相反,順序也相反。

  • 登出cdev結構
void cdev_del(struct cdev *p);
  • 釋放裝置號
void unregister_chrdev_region(dev_t from, unsigned count);
  • 釋放cdev解構空間
kfree(void *p);

驅動核心

  • 實現struct file_operations結構

示例

程式碼

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/kdev_t.h>
#include<linux/slab.h>

#define MY_DEVICE_NAME "linux26"
static struct cdev *pcdev;
static dev_t dev_no;   //第一個裝置號(包含主次)
static int major=0;

ssize_t my_device_open(struct inode *node, struct file *file)
{
   printk("my device is open\n");
   return 0;
}
ssize_t my_device_close(struct inode *node, struct file *file)
{
  printk("my device is close\n");
  return 0;
}

ssize_t my_device_read(struct file *fp, char __user *buf, size_t size, loff_t *loff)
{
  printk("my device is read\n");
  return 0;
}

ssize_t my_device_write(struct file *file, const char __user *buf, size_t size, loff_t *loff)
{
  printk("my device is write\n");
  return 0;
}


//檔案結構體
struct file_operations my_fops={
    .owner=THIS_MODULE,
    .open=my_device_open,
    .read=my_device_read,
    .write=my_device_write,
    .release=my_device_close,
};


static int __init linux26_cdev_init(void)
{
  //分配cdev空間
  pcdev=cdev_alloc();
  //動態分配裝置號 次裝置號0開始 數量為2個 名稱為巨集
  alloc_chrdev_region(&dev_no,0,2,MY_DEVICE_NAME);
  //初始化結構體
  cdev_init(pcdev,&my_fops);
  //註冊驅動
  cdev_add(pcdev,dev_no,2);
  //再去查註冊檔案很麻煩,這裡列印主裝置號方便建立節點
  major=MAJOR(dev_no);
  printk("linux26 major is %d\n",major);
  return 0;
}
static void __exit linux26_cdev_exit(void)
{
  cdev_del(pcdev);
  unregister_chrdev_region(dev_no,2);
  kfree(pcdev);
  printk("cdev module is exit\n");
}

module_init(linux26_cdev_init);
module_exit(linux26_cdev_exit);
MODULE_LICENSE("GPL");

app程式碼

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main(int argc,char *argv[])
{
  char buf[20];
  int fd = open(argv[1],O_RDWR);
    if(fd == -1)
    {
        printf("開啟失敗\n");
        return 0;
    }
    read(fd,buf,0);
    write(fd,NULL,0);
    close(fd);
    return 0;
}

Makefile

KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
    make -C $(KERN_DIR) M=`pwd` modules
clean:
    rm -f *.mod.o *.order *.symvers *.o *.mod.c
obj-m += cdev.o

模組編譯

[[email protected] zhangchao]# make
make -C /zhangchao/linux3.5/linux-3.5 M=`pwd` modules
make[1]: 進入目錄“/zhangchao/linux3.5/linux-3.5”
  CC [M]  /zhangchao/rootfs/zhangchao/cdev.o
  Building modules, stage 2.
  MODPOST 1 modules
  LD [M]  /zhangchao/rootfs/zhangchao/cdev.ko
make[1]: 離開目錄“/zhangchao/linux3.5/linux-3.5”
[[email protected] zhangchao]# ls
cdev_app.c  cdev.ko     cdev.mod.o  Makefile       Module.symvers
cdev.c      cdev.mod.c  cdev.o      modules.order
[[email protected] zhangchao]# 

app 編譯

[[email protected] zhangchao]# arm-linux-gcc cdev_app.c -o cdevapp
[[email protected] zhangchao]# ls
cdevapp     cdev.c   cdev.mod.c  cdev.o    modules.order
cdev_app.c  cdev.ko  cdev.mod.o  Makefile  Module.symvers

開發板裝載模組

沒有采取自動建立裝置節點,所以/dev沒有該模組的裝置檔案節點

[root@ZC/zhangchao]#insmod cdev.ko 
[  742.520000] linux26 major is 250
[root@ZC/zhangchao]#lsmod
Module                  Size  Used by    Tainted: G  
cdev                    1518  0 
[root@ZC/zhangchao]#

通過/proc/devices設備註冊檔案表檢視是否安裝成功

可以看到主裝置號為250的linux26裝置識別名出現在列表中,證明註冊沒有問題。

[root@ZC/zhangchao]#cat /proc/devices 
Character devices:
  1 mem
  2 pty
  3 ttyp
  ···
189 usb_device
204 ttySAC
216 rfcomm
250 linux26
251 ttyGS
252 watchdog
253 media
254 rtc

Block devices:
  1 ramdisk
···
179 mmc
254 device-mapper

建立檔案節點

[root@ZC/zhangchao]#mknod /dev/linux26 c 250 1
[root@ZC/zhangchao]#ls /dev/linux26 -l
crw-r--r--    1 root     root      250,   1 Mar 29 16:37 /dev/linux26
[root@ZC/zhangchao]#

執行app

app呼叫驅動模組成功

[root@ZC/zhangchao]#./cdevapp /dev/linux26 
[ 1345.240000] my device is open
[ 1345.240000] my device is read
[ 1345.240000] my device is write
[ 1345.240000] my device is close
[root@ZC/zhangchao]#

思考下面的問題

[root@ZC/zhangchao]#rm /dev/linux26 
[root@ZC/zhangchao]#mknod /dev/linux26 c 250 2
[root@ZC/zhangchao]#./cdevapp /dev/linux26 
開啟失敗
[root@ZC/zhangchao]#rm /dev/linux26 
[root@ZC/zhangchao]#mknod /dev/linux26 c 250 1
[root@ZC/zhangchao]#./cdevapp /dev/linux26 
[ 1564.045000] my device is open
[ 1564.045000] my device is read
[ 1564.045000] my device is write
[ 1564.045000] my device is close
[root@ZC/zhangchao]#

如果將次裝置號定義為2,執行app將不能執行驅動模組,這是為什麼呢?因為我們註冊時,申請的次裝置號數量為兩個,在主裝置號下其他次裝置號沒有被佔用的情況下,所以只有0和1可以被註冊,裝置號為2時,他將是次裝置號的第三個

解除安裝驅動模組

[root@ZC/zhangchao]#rmmod cdev.ko 
[ 1977.955000] cdev module is exit
[root@ZC/zhangchao]#

相關推薦

linux2.6標準字元裝置驅動模型手動註冊

linux2.6標準字元裝置核心結構 //雖然linux26有核心結構體,但在程式碼中沒有具體操作,他們的呼叫在相關注冊函式中對其有操作 struct cdev { struct kobject kobj; struct module *o

從解析DTS到建立device_從device_node到併入裝置驅動模型結合原始碼

從device_node到併入裝置驅動模型 此篇部落格有很多參考其他文章的內容,由於參考內容繁雜,不一一標註角標了,在末尾會貼上所有參考部落格的link,如有侵權,請聯絡本人處理,謝謝。 深入,並且廣泛 -沉默犀牛 上一篇文章已經詳細的分析了兩個問題: 1.如何根據

Linux字元裝置驅動模型--字元裝置註冊

當我們編寫字元裝置驅動程式的時候,在進行字元裝置物件cdev的分配、初始化,裝置號的註冊這些初始化階段之後,就可以將它加入到系統中,這樣才能使用這個字元裝置。將一個字元裝置加入到系統中呼叫的函式時cdev_add,核心原始碼如下: int cdev_add(struct cdev *

Linux 字元裝置驅動結構—— 自動建立裝置節點

      上一篇我們介紹到建立裝置檔案的方法,利用cat /proc/devices檢視申請到的裝置名,裝置號。 第一種是使用mknod手工建立:mknod filename type major minor 第二種是自動建立裝置節點:利用u

Linux 字元裝置驅動結構—— cdev 結構體、裝置號相關知識解析

一、字元裝置基礎知識 1、裝置驅動分類       linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式: 字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。

Linux:驅動字元裝置工作原理未完

字元裝置驅動工作原理 系統整體工作原理 應用層->API->裝置驅動->硬體? API:open、read、write、close等? 驅動原始碼中提供真正的open、read、write、close等函式實體? file_

6.1Linux字元裝置驅動結構

cdev結構體 用於描述字元裝置 1 struct cdev 2 { 3 struct kobject kobj; /* 內嵌的kobject物件*/ 4 struct module *owner; /*所屬模組*/ 5 struct file_operations *o

linux字元裝置驅動模型

一.雜項裝置驅動模型 雜項的主裝置號固定為10,只有255個次裝置號。可以直接生成驅動核心。 1.需要確定每個模型都會用到的檔案操作方法集合指標 2.確定核心的結構體 static struct miscdevice abc 確定三個引數,第一個為次裝置號,第二個是次裝

Linux 字元裝置驅動結構—— file、inode結構體及chardevs陣列等相關知識解析

       先看下面這張圖,這是Linux 中虛擬檔案系統、一般的裝置檔案與裝置驅動程式值間的函式呼叫關係;         上面這張圖展現了一個應用程式呼叫字元裝置驅動的過程, 在裝置驅動程式的設計中,一般而言,會關心 file 和 inode 這兩個結構體  

Linux 字元裝置驅動結構—— file、inode結構體及chardevs陣列等相關知識解析[轉載]

       先看下面這張圖,這是Linux 中虛擬檔案系統、一般的裝置檔案與裝置驅動程式值間的函式呼叫關係;        上面這張圖展現了一個應用程式呼叫字元裝置驅動的過程, 在裝置驅動程式的設計中,一般而言,會關心 file 和 inode 這兩個結構體       

普通字元裝置驅動的兩種註冊方式新&舊

普通字元裝置驅動的兩種註冊方式(新&舊) 在核心中,對於一個普通的字元裝置驅動,不難發現有兩種註冊方式: register_chrdev族函式+建立裝置類、檔案的函式:這種方法是2.4版本

Linux 字元裝置驅動結構—— cdev 結構體、裝置號相關知識解析[轉載]

一、字元裝置基礎知識1、裝置驅動分類      linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式:字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。字元裝置是面向流的裝置,常見的字元裝置有滑鼠

Linux 字元裝置驅動結構—— file_operations 結構體知識解析

        前面在 Linux 字元裝置驅動開發基礎 (三)—— 字元裝置驅動結構(中) ,我們已經介紹了兩種重要的資料結構 struct inode{...}與 struct file{...} ,下面來介紹另一個比較重要資料結構 struct _file_oper

Linux2.6.35.7字元裝置驅動註冊介面

1. 老介面 (1)註冊函式 static inline int register_chrdev(unsigned int major, const char *name, const struct f

20 字元裝置驅動相關的函式和引數及實現虛擬檔案

字元裝置驅動相關的函式和引數及實現(虛擬檔案) 使用者程序呼叫函式順序: open ---> kernel ---> cdev.ops->open(..) read ---> kernel ---> cdev.ops->read(

裝置驅動模型之:kobject,kset,ktype

本篇部落格介紹kset與kobject/kset之間的關係,好了,廢話不多說,直接上ktype的結構體: struct kobj_type { void (*release)(struct kobject *kobj); const struct sysf

裝置驅動模型之:kobject,kset,ktype

在《裝置驅動模型之:kobject,kset,ktype(四)》這篇博文裡面已經詳細介紹了kset的操作以及kset與kobject的關係,下面則是對於這篇博文的一些實際操作: #include <linux/module.h> #include <linux/init

裝置驅動模型之:kobject,kset,ktype

概述 kobject結構是linux驅動程式的基礎,也是裝置模型中抽象的一部分。如果想要了解驅動程式必須瞭解kobject結構的具體資料組成以及kobject結構的作用。核心為了相容各種形形色色的裝置,必須對各種裝置的共同特性進行抽象。這種抽象在C++中稱之為基礎類,但是C語言沒有繼承特性,

linux裝置驅動3字元驅動 -按鍵查詢法

本文描述查詢法。 所謂查詢法,就是在應用程式裡面執行 while (1) { read(fd, key_vals, sizeof(key_vals)); ... } 載入驅動並在後臺執行應用程式時, 通過top可以看到CPU利用率,該應用程序佔用9

linux裝置驅動模型字元裝置open系統呼叫流程

從前面 的例子可以看到,我們在操作一個調和時都 是通過open系統呼叫先去開啟這個裝置,不管是裝置還是檔案,我們要訪問它都要稱通過open函式來先開啟, 這樣才能呼叫其它的函式如read、write來操作它,即通知核心新建一個代表該檔案的結構,並且返回該檔案的描述符(一個整