1. 程式人生 > >Linux字符設備簡單示例

Linux字符設備簡單示例

推薦 9.png lock 設計模型 函數 選擇 ini use cde

1. Linux字符設備是一種按字節來訪問的設備,字符驅動則負責驅動字符設備,這樣的驅動通常實現open、close、read和write系統調用。例如:串口、Led、按鍵等。

2. 通過字符設備文件(/dev/),應用程序可以使用相應的字符設備驅動來控制字符設備

技術分享圖片

3. 創建字符設備文件的方法一般有兩種

(1)使用命令mknod : mknod /dev/文件名 c 主設備號 次設備號 (查看主設備號:cat /proc/devices)

(2)使用函數在驅動程序中創建

4. 字符設備通用設計模型

技術分享圖片

5. 在任何一種驅動模型中,設備都會用內核中的一種結構來描述。字符設備在內核中使用struct cdev來描述

struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;  //設備操作函數集
    struct list_head list;
    dev_t dev;                //設備號
    unsigned int count;           //設備數
};

6. Linux內核中使用dev_t類型來定義設備號,dev_t其實質為32位unsigned int類型,其中高12位為主設備號,低20位為此設備號。

(1)MKDEV(主設備號,此設備號)

(2)MAJOR(dev_t dev)

(3)MINOR(dev_t dev)

註:字符設備文件與字符設備驅動是通過主設備號建立對應關系;驅動程序用此設備號來區分同類型的設備

7. 設備號的申請與註銷

(1)靜態申請:開發者自己選擇一個數字作為主設備號,通過函數 register_chardev_region 向內核申請

(2)動態分配:使用 alloc_chrdev_region 由內核分配一個可用的主設備號(推薦使用)

(3)不論使用何種方法分配設備號,都應該在驅動退出時,使用 unregister_chrdev_region 函數釋放這些設備

8. 操作函數集:struct file_operations是一個函數指針的集合,定義能在設備上進行的操作。

9. 字符設備描述結構的分配、註冊與註銷

(1)cdev變量的定義可以采用靜態和動態兩種方法

  * 靜態分配:struct cdev mdev;

  * 動態分配:struct cdev* pdev = cdev_alloc();(可以通過命令:cat /proc/devices查看主設備號)

(2)cdev變量的初始化使用cdev_init()函數來完成

  void cdev_init(struct cdev *cdev, const struct file_operations *fops)

  cdev: 待初始化的cdev結構

  fops: 設備對應的操作函數集

(3)字符設備的註冊使用cdev_add()函數來完成

(4)字符設備的註銷使用cdev_del()函數來完成

10. Linux驅動中幾個重要的數據結構

(1)在Linux系統中,每一個打開的文件,在內核中都會關聯一個struct file結構,它是由內核在打開文件時創建,在文件關閉後釋放。

  struct file結構中的重要成員

  * struct file_operations* f_op;  //文件操作函數集

  * loff_t f_pos;         //文件讀寫指針

(2)每一個存在於文件系統中的文件都會關聯一個inode結構,該結構主要用來記錄文件物理上的信息。因此,它和代表打開文件的file結構是不同的,一個文件沒有被打開時不會關聯file結構,但是會關聯一個inode結構(存於磁盤,操作文件時在內存中建立相應的映射結構)

註:inode用於存儲文件的元信息(除了文件名的所有信心),中文譯名索引節點

11. 設備操作:struct file_operations

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
};

(1)open: int (*open) (struct inode *, struct file *);(打開設備,響應open系統調用)
   open方法是驅動程序用來為以後的操作完成初始化準備工作的。在大部分驅動程序紅,open主要完成以下工作:

  * 標明次設備號

  * 啟動設備

(2)release: int (*release) (struct inode *, struct file *);(關閉設備,響應close系統調用)

(3)llseek: loff_t (*llseek) (struct file *, loff_t, int);(重定位讀寫指針,響應lseek系統調用)

(4)read:ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);(從設備讀取數據,響應read系統調用)

  ① read設備方法通常完成兩件事情

  * 從設備中讀取數據(屬於硬件訪問類操作)

  * 將讀取到的數據返回給應用程序

  ② 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)

技術分享圖片


(5)write:ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);(向設備寫入數據,響應write系統調用)
  ① write設備方法通常完成2件事情

  * 從應用程序提供的地址中取出數據

  * 將數據寫入設備(屬於硬件訪問類操作)

  ② 其參數類似於read

12. 字符設備簡單示例

①驅動程序 MemDev.c

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/slab.h>

/* We suppose this is the two device‘s registers */
int dev1_registers[5];
int dev2_registers[5];

struct cdev cdev; 
dev_t devno;

/*文件打開函數*/
int mem_open(struct inode *inode, struct file *filp)
{
    /*獲取次設備號*/
    int num = MINOR(inode->i_rdev);
    
    if (num == 0)
        filp->private_data = dev1_registers;
    else if(num == 1)
        filp->private_data = dev2_registers;
    else
        return -ENODEV;  //無效的次設備號
    
    return 0; 
}

/*文件釋放函數*/
int mem_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/*讀函數*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    int *register_addr = filp->private_data; /*獲取設備的寄存器基地址*/

    /*判斷讀位置是否有效*/
    if (p >= 5 * sizeof(int))
        return 0;
    if (count > 5 * sizeof(int) - p)
        count = 5 * sizeof(int) - p;

  /*讀數據到用戶空間*/
    if (copy_to_user(buf, register_addr + p, count))
    {
        ret = -EFAULT;
    }
    else
    {
        *ppos += count;
        ret = count;
    }
    
    return ret;
}

/*寫函數*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
    unsigned long p =  *ppos;
    unsigned int count = size;
    int ret = 0;
    int *register_addr = filp->private_data; /*獲取設備的寄存器地址*/
  
    /*分析和獲取有效的寫長度*/
    if (p >= 5*sizeof(int))
        return 0;
    
    if (count > 5 * sizeof(int) - p)
        count = 5 * sizeof(int) - p;
    
    /*從用戶空間寫入數據*/
    if (copy_from_user(register_addr + p, buf, count))
        ret = -EFAULT;
    else
    {
        *ppos += count;
        ret = count;
    }
    
    return ret;
}

/* seek文件定位函數 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{ 
    loff_t newpos;

    switch(whence) {
        case SEEK_SET: 
            newpos = offset;
            break;

        case SEEK_CUR: 
            newpos = filp->f_pos + offset;
            break;

        case SEEK_END: 
            newpos = 5 * sizeof(int) - 1 + offset;
            break;
        
        default: 
            return -EINVAL;
    }
    
    if ((newpos < 0) || (newpos > 5 * sizeof(int)))
        return -EINVAL;
        
    filp->f_pos = newpos;
    
    return newpos;
}

/*文件操作結構體*/
static const struct file_operations mem_fops =
{
    .open = mem_open,    
    .read = mem_read,
    .write = mem_write,
    .llseek = mem_llseek,
    .release = mem_release,
};

/*設備驅動模塊加載函數*/
static int memdev_init(void)
{
    /*初始化cdev結構*/
    cdev_init(&cdev, &mem_fops);
  
    /* 註冊字符設備 */
    alloc_chrdev_region(&devno, 0, 2, "memdev");
    
    cdev_add(&cdev, devno, 2);
}

/*模塊卸載函數*/
static void memdev_exit(void)
{
    cdev_del(&cdev);   /*註銷設備*/
    unregister_chrdev_region(devno, 2); /*釋放設備號*/
}

MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);


②測試代碼MemWrite.c

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

int main()
{
    int fd = 0;
    int src0[] = {0, 1, 2, 3, 4};
    int src1[] = {10, 11, 12, 13, 14};
    
    /*打開設備文件*/
    fd = open("/dev/memdev0", O_RDWR);
    
    /*寫入數據*/
    write(fd, src0, sizeof(src0));
    
    /*關閉設備*/
    close(fd);
    
    fd = open("/dev/memdev1", O_RDWR);
    
    /*寫入數據*/
    write(fd, src1, sizeof(src1));
    
    /*關閉設備*/
    close(fd);
    
    return 0;    
}


③測試代碼MemRead.c

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

int main()
{
    int fd = 0;
    int dst = 0;
    
    /*打開設備文件*/
    fd = open("/dev/memdev0", O_RDWR);
    
    lseek(fd, 2, SEEK_SET);
    
    /*寫入數據*/
    read(fd, &dst, sizeof(int));
    
    printf("dst0 is %d\n", dst);
    
    /*關閉設備*/
    close(fd);
    
    /*打開設備文件*/
    fd = open("/dev/memdev1", O_RDWR);
    
    lseek(fd, 3, SEEK_SET);
    /*寫入數據*/
    read(fd, &dst, sizeof(int));
    
    printf("dst1 is %d\n", dst);
    
    /*關閉設備*/
    close(fd);
    
    return 0;    
}

④ 測試步驟

(1)安裝驅動模塊:insmod MemDev.ko

(2)查看主設備號:cat /proc/devices(查找memdev對應的主設備號)

(3)創建設備文件:mknod /dev/memdev0 c 主設備號 0

(4)運行測試代碼進行測試

Linux字符設備簡單示例