首頁 > 軟體

Linux字元裝置-簡單字元裝置模型

2020-06-16 17:45:58

Linux字元裝置

一. 使用字元裝置驅動

 

1. 編譯/安裝驅動
在Linux系統中,驅動程式通常採用核心模組的程式結構來進行編碼。因此,編譯/安裝一個驅動程式,其實質就是編譯/安裝一個核心模組。

2. 字元裝置檔案

通過字元裝置檔案,應用程式可以使用相應的字元裝置驅動程式來控制字元裝置。

 


建立字元裝置檔案的方法一般有兩種:
1.使用mknod命令
mknod /dev/檔名 c 主裝置號 次裝置號
2. 使用函數在驅動程式中建立

二. 字元驅動程式設計模型

 

  1. 裝置描述結構

 

  1. 驅動模型
    在Linux系統中,裝置的型別非常繁多,如:字元裝置,塊裝置,網路介面裝置,USB裝置,PCI裝置,平台裝置,混雜裝置……,而裝置型別不同,也意味著其對應的驅動程式模型不同,這樣就導致了我們需要去掌握眾多的驅動程式模型。那麼能不能從這些眾多的驅動模型中提煉出一些具有共性的規則,則是我們能不能學好Linux驅動的關鍵。

 

1. 裝置描述結構

在任何一種驅動模型中,裝置都會用核心中的一種結構來描述。我們的字元裝置在核心中使用structcdev來描述。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; //裝置操作集
struct list_head list;
dev_t dev; //裝置號
unsigned int count; //裝置數
};

1.1 裝置號

檢視/dev目錄下裝置號

 

1.1 次裝置號

 

2.1 裝置號-操作

Linux核心中使用dev_t型別來定義裝置號,dev_t這種型別其實質為32位元的unsigned int,其中高12位元為主裝置號,低20位為次裝置號.
問1:如果知道主裝置號,次裝置號,怎麼組合成dev_t型別
答:dev_t dev = MKDEV(主裝置號,次裝置號)

問2: 如何從dev_t中分解出主裝置號?
答: 主裝置號 = MAJOR(dev_t dev)
問3: 如何從dev_t中分解出次裝置號?
答: 次裝置號=MINOR(dev_t dev)

1.1 裝置號-分配

如何為裝置分配一個主裝置號?
靜態申請
開發者自己選擇一個數位作為主裝置號,然後通過函數register_chrdev_region向核心申請使用。缺點:如果申請使用的裝置號已經被核心中的其他驅動使用了,則申請失敗。
動態分配
使用alloc_chrdev_region由核心分配一個可用的主裝置號。
優點:因為核心知道哪些號已經被使用了,所以不會導致分配到已經被使用的號。

1.1 裝置號-登出

不論使用何種方法分配裝置號,都應該在驅動退出時,使用unregister_chrdev_region
函數釋放這些裝置號。

1.2 操作函數集

 

2.2 操作函數集

Struct file_operations是一個函數指標的集合,定義能在
裝置上進行的操作。結構中的函數指標指向驅動中的函數,
這些函數實現一個針對裝置的操作, 對於不支援的操作則設
置函數指標為 NULL。例如:
struct file_operations dev_fops = {

.llseek = NULL,
.read = dev_read,
.write = dev_write,
.ioctl = dev_ioctl,
.open = dev_open,
.release = dev_release,
};

2.1 字元裝置初始化

 

2.1 描述結構-分配

cdev變數的定義可以採用靜態和動態兩種辦法
• 靜態分配
struct cdev mdev;
• 動態分配
struct cdev *pdev = cdev_alloc();

2.1 描述結構-初始化

struct cdev的初始化使用cdev_init函數來完成。
cdev_init(struct cdev *cdev, const struct file_operations *fops)

引數:
cdev: 待初始化的cdev結構
fops: 裝置對應的操作函數集

2.1 描述結構-註冊

字元裝置的註冊使用cdev_add函數來完成。
cdev_add(struct cdev *p, dev_t dev, unsigned count)

引數:
p: 待新增到核心的字元裝置結構
dev: 裝置號
count: 該類裝置的裝置個數

2.1 硬體初始化

根據相應硬體的晶片手冊,完成初始化。

2.2 實現裝置操作

 

2.2 手把手帶你來分析

分析
file_operations

2.2 裝置操作原型

int (*open) (struct inode *, struct file *)
開啟裝置,響應open系統


int (*release) (struct inode *, struct file *);
關閉裝置,響應close系統呼叫


loff_t (*llseek) (struct file *, loff_t, int)
重定位讀寫指標,響應lseek系統呼叫

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *)
從裝置讀取資料,響應read系統呼叫


ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)
向裝置寫入資料,響應write系統呼叫

2.2 Struct file

在Linux系統中,每一個開啟的檔案,在核心中都會關聯一個struct file,它由核心在開啟檔案時建立, 在檔案關閉後釋放。

重要成員:
loff_t f_pos /*檔案讀寫指標*/
struct file_operations *f_op /*該檔案所對應的操作*/

2.2 Struct inode

每一個存在於檔案系統裡面的檔案都會關聯一個inode 結構,該結構主要用來記錄檔案物理上的資訊。因此, 它和代表開啟檔案的file結構是不同的。一個檔案沒有被開啟時不會關聯file結構,但是卻會關聯一個inode 結構。
重要成員:
dev_t i_rdev:裝置號

3.2 裝置操作-open

open裝置方法是驅動程式用來為以後的操作完成初始化準備工作的。在大部分驅動程式
中,open完成如下工作:
標明次裝置號
啟動裝置

3.2 裝置操作-release

release方法的作用正好與open相反。這個裝置方法有時也稱為close,它應該:
關閉裝置。

3.2 裝置操作-read

read裝置方法通常完成2件事情:
從裝置中讀取資料(屬於硬體存取類操作)
將讀取到的資料返回給應用程式
ssize_t (*read) (struct file *filp, char __user *buff, size_t count, loff_t *offp)
引數分析:
filp:與字元裝置檔案關聯的file結構指標, 由核心建立。
buff : 從裝置讀取到的資料,需要儲存到的位置。由read系統呼叫提供該引數。
count: 請求傳輸的資料量,由read系統呼叫提供該引數。
offp: 檔案的讀寫位置,由核心從file結構中取出後,傳遞進來。

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)

 

3.2 裝置操作-write

write裝置方法通常完成2件事情:
從應用程式提供的地址中取出資料將資料寫入裝置(屬於硬體存取類操作)
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)
其引數類似於read

2.3 驅動登出

 

當我們從核心中解除安裝驅動程式的時候,需要使用cdev_del函數來完成字元裝置的登出。

先寫一個自動分配字元裝置號手動分配字元裝置節點的例子及APP

手動安裝步驟:

Insmod char_dev.ko

檢視字元裝置號

cat /proc/devices

 

再安裝裝置節點

mknod /dev/my_chardev c 248 0

然後是測試app

./my_char_dev_app 1

核心驅動程式碼char_dev.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <asm/device.h>  //下面這三個標頭檔案是由於動態建立需要加的
#include <linux/device.h>
#include <linux/cdev.h>
#include "my_cdev.h"
struct cdev cdev;
dev_t devno;//這裡是動態分配裝置號

int my_cdev_open(struct inode *node,struct file *filp)
{
    printk("my_cdev_open sucess!n");
    return 0;
}

long my_cdev_ioctl(struct file *filp ,unsigned int cmd ,unsigned long arg)
{
    switch(cmd)
    {
        case LED_ON:
            printk("LED_ON is set!n");
            return 0;
        case LED_OFF:
            printk("LED_OFF is set!n");
            return 0;
        default :
            return -EINVAL;
    }
}

struct file_operations my_cdev_fops=
{
    .open = my_cdev_open,
    .unlocked_ioctl = my_cdev_ioctl,

};

static int my_cdev_init(void)
{
    int ret;
    /**動態分配裝置號*/
    ret = alloc_chrdev_region(&devno,0,1,"my_chardev");
    if(ret)
    {
        printk("alloc_chrdev_region fail!n");
        unregister_chrdev_region(devno,1);
        return ret;
    }
    else
    {
        printk("alloc_chrdev_region sucess!n");
    }
    /**描述結構初始化*/
    cdev_init(&cdev,&my_cdev_fops);
    /**描述結構註冊*/
    ret = cdev_add(&cdev,devno,1);
    if(ret)
    {
        printk("cdev add fail.n");
        unregister_chrdev_region(devno,1);
        return ret;
    }
    else
    {
        printk("cdev add sucess!n");
    }
   
    return 0;
}
static void my_cdev_exit(void)
{
    cdev_del(&cdev);
    unregister_chrdev_region(devno,1);
    printk("my_cdev_exit sucess!n");
}
module_init(my_cdev_init);
module_exit(my_cdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YEFEI");
MODULE_DESCRIPTION("YEFEI Driver");

APP標頭檔案my_cdev.h

#ifndef __MY_CDEV_H__
#define __MY_CDEV_H__

#define LED_MAGIC 'L'
#define LED_ON _IO(LED_MAGIC,0)
#define LED_OFF _IO(LED_MAGIC,1)

#endif

APP測試檔案my_char_dev_app.c

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include "my_cdev.h"

int main(int argc,char *argv[])
{
    int fd;
    int cmd;
    if(argc < 2)
    {
        printf("Please enter secend param!n");
        return 0;
    }
    cmd = atoi(argv[1]);
    fd = open("/dev/my_chardev",O_RDWR);
    if(fd < 0)
    {
        printf("Open dev/my_chardev fail!n");
        close(fd);
        return 0;
    }
    switch(cmd)
    {
        case 1:
            ioctl(fd,LED_ON);
            break;
        case 2:
            ioctl(fd,LED_OFF);
            break;
        default:
            break;
    }
    close(fd);
    return 0;
}

2016-02-17
21:54:48

本文永久更新連結地址http://www.linuxidc.com/Linux/2016-02/128601.htm


IT145.com E-mail:sddin#qq.com