linux 驅動程序 設備模塊 設備號 設備文件建立 設備註冊 字符驅動設備分析

驅動程序分爲三部分:驅動設計模式(linux規定的)內核相關模塊 硬件知識 。設備文件存放目錄在 /dev下面node

驅動模塊分類:

  • 字符驅動模塊(高):以字節爲最小訪問單位,經常實現open,read,writelinux

  • 網絡驅動模塊:以塊爲最小訪問單位,通常最小塊512字節,能夠是512字節的整數倍,可是linux容許塊設備訪問任意字節。
    設計模式

  • 塊設備驅動模塊:網絡事務
    緩存

驅動模塊添加方式 手動和編譯進內核

  • 模塊手動命令添加到內核,.ko文件。insmod 網絡

  • 編譯進內核,app

    1,vi Kconfig 函數

    config HELLO (HELLO 模塊名字)測試

        bool "hello driver" (menuconfig 菜單中出現的名字)
spa

    保存,make menuconfig 就會出現新的選項 hello driver設計

    2,vi Makefile 

                設備操做方式

設備號用來區分設備文件。分爲主設備號和次設備號

  • 主設備號:字符設備文件與字符設備驅動鏈接

  • 次設備號:因爲設備可能多個,能夠用次設備號,區分哪一個設備。

設備號關鍵字dev_t  爲unsigned int 32類型, 高12位主設備號,低20位爲次設備號

MAJOR(dev_t dev)能夠獲得主設備,MINOR(dev_t dev)獲得次設備

linux如何分配主設備號 靜態和動態分配

  • 靜態申請

    開發者本身申請 register_chrdev_region函數註冊設備號

    優勢:簡單

    缺點:容易重複衝突

  • 動態申請

    內核分配 alloc_chrdev_region函數分配

    優勢:易於驅動推廣

    缺點:沒法在安裝驅動前建立設備文件

註銷設備號 unregister_chrdev_region()釋放設備號

linux建立設備文件 手動mknod和自動class_create device_create

  • 使用mknod手工建立

    mknod filename type major minor

    mknod serial0 c 100 0 ;建立名字爲 serial0 字符設備 主設備號100,次設備號0 

  • 自動建立 

    利用udev(mdev)來實現設備文件的自動建立,首先應保證支持udev(mdev),由busybox配置。在驅動初始

    化代碼裏調用class_create爲該設備建立一個class,再爲每一個設備調用device_create建立對應的設備。

設備操做中幾個重要結構

  • Struct File;表明一個打開的文件,系統每打開一個文件就關聯一個struct file,文件關閉時釋放

  • loff_t f_pos ;文件讀寫位置,文件讀寫指針位置是變化的

    struct file_operations *f_op ;操做函數指針的集合,.參數:指針指向後面的函數 用於 用戶空間與驅動程序進行通訊

    

    內核操做使用上面變量時,就會給.owner 的THIS_MODULE加1。引用計數,能夠經過lsmod 查看。只有模塊計數爲       0,才能夠卸載模塊

    上面函數名字能夠隨便起;mem_read可是函數參數不能改,如

    

    參數第一個是文件指針fd,第二個緩存,第三個是個數,第四個是文件位置

  • Struct Inode :文件記錄了設備信息,一個文件一個Inode

設備註冊 步驟

字符設備使用struct cdev描述

  • 分配cdev空間,使用指針時須要 struct cdev *cdev_alloc(void)分配內存

  • 初始化cdev。cdev_init(cdev,ops);

  • 內核添加cdev。cdev_add(設備結構,設備號,設備個數),設備文件與設備號鏈接起來了

  • 設備註銷 cdev_del(struct cdev *p);

內核與用戶空間指針進行傳值的專門函數copy_from_user(),copy_to_user(),他們之間不能直接傳值

設備添加/卸載的結構圖

例子

/*********************************************/
#ifndef _MEMDEV_H_
#define _MEMDEV_H_
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 190   /*預設的mem的主設備號*/
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2    /*設備數*/
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096//分配內存的大小
#endif
/*mem設備描述結構體*/
struct mem_dev                                     
{                                                        
  char *data; //分配到的內存的起始地址                     
  unsigned long size;  //內存的大小 
};
#endif /* _MEMDEV_H_ */
/*********************************************/

dev.c

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include "memdev.h"
static mem_major = MEMDEV_MAJOR;
module_param(mem_major, int, S_IRUGO);
struct mem_dev *mem_devp; /*設備結構體指針*/
struct cdev cdev; 
/*文件打開函數*/
int mem_open(struct inode *inode, struct file *filp)
{
    struct mem_dev *dev;
    
    /*獲取次設備號*/
    int num = MINOR(inode->i_rdev);// inode->i_rdev包含實際的設備編號
    if (num >= MEMDEV_NR_DEVS) 
            return -ENODEV;
    dev = &mem_devp[num];
    
    /*將設備描述結構指針賦值給文件私有數據指針*/
    filp->private_data = dev;//使用這個成員來指向分配的數據
    
    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)//buf緩存區,size讀取文件大小,ppos當前讀寫位置
{
  unsigned long p =  *ppos;//p爲當前讀寫位置
  unsigned int count = size;//一次讀取的大小
  int ret = 0;
  struct mem_dev *dev = filp->private_data; /*得到設備結構體指針*/
  /*判斷讀位置是否有效*/
  if (p >= MEMDEV_SIZE)//是否超出讀取獲圍
    return 0;
  if (count > MEMDEV_SIZE - p)
    count = MEMDEV_SIZE - p;//count大於可讀取的範圍,則縮小讀取範圍。
  /*讀數據到用戶空間*/
  if (copy_to_user(buf, (void*)(dev->data + p), count))//返回buf,讀取位置,讀取數量
  {
    ret =  - EFAULT;
  }
  else
  {
    *ppos += count;//將文件當前位置向後移
    ret = count;//返回實際讀取字節數
    
    printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
  }
  return ret;//返回實際讀取字節數,判斷讀取是否成功
}
/*寫函數*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)//write和read相似,直接參考read
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  struct mem_dev *dev = filp->private_data; /*得到設備結構體指針*/
  
  /*分析和獲取有效的寫長度*/
  if (p >= MEMDEV_SIZE)
    return 0;
  if (count > MEMDEV_SIZE - p)
    count = MEMDEV_SIZE - p;
    
  /*從用戶空間寫入數據*/
  if (copy_from_user(dev->data + p, buf, count))
    ret =  - EFAULT;
  else
  {
    *ppos += count;
    ret = count;
    
    printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
  }
  return ret;
}
/* seek文件定位函數 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)//做改變文件中的當前讀/寫位置, 而且新位置做爲(正的)返回值在測試程序中要從新定位文件位置,whence這裏被設置爲 SEEK_SET
{ 
    loff_t newpos;
    switch(whence) {
      case 0: /* SEEK_SET */
        newpos = offset;//從文件頭開始定位
        break;
      case 1: /* SEEK_CUR */
        newpos = filp->f_pos + offset;//從文件中間定位
        break;
      case 2: /* SEEK_END */
        newpos = MEMDEV_SIZE -1 + offset;//從文件尾開始定位,因爲是從0開始,因此要減1
        break;
      default: /* can't happen */
        return -EINVAL;
    }
    if ((newpos<0) || (newpos>MEMDEV_SIZE))
     return -EINVAL;
     
    filp->f_pos = newpos;//返回當前文件位置
    return newpos;
}
/*文件操做結構體*/
static const struct file_operations mem_fops =
{
  .owner = THIS_MODULE,
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
};
/*設備驅動模塊加載函數*/
static int memdev_init(void)  //初始化模塊
{
  int result;
  int i;
  dev_t devno = MKDEV(mem_major, 0);//MKDEV是將主設備號和次設備號轉換爲dev_t類型數據,參數mem_major在頭文件中預設爲254
 
  /* 靜態申請設備號*/
  if (mem_major)//memdev.h 中定義了爲254。因此本例爲靜態分配主設備號254
    result = register_chrdev_region(devno, 2, "memdev");//devno爲主設備號,共申請兩個連續的設備,設備名爲"memdev"
  else  /* 動態分配設備號 */
  {
    result = alloc_chrdev_region(&devno, 0, 2, "memdev");//&devno做爲一個輸出參數,次設備號從0開始分配,申請2個設備,設備名爲"memdev"
    mem_major = MAJOR(devno);//獲取動態分配到的主設備號。
  }  
  
  if (result < 0)//result返回0時爲申請成功,反加負值爲申請失敗。
    return result;
  /*初始化cdev結構*/
  cdev_init(&cdev, &mem_fops);//初始化cdev結構,將結構體cdev和mem_fops綁定起來
  cdev.owner = THIS_MODULE;//驅動引用計數,做用是這個驅動正在使用的時候,你再次用inmod命令時,出現警告提示
  cdev.ops = &mem_fops;
  
  /* 註冊字符設備 */
  cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);//MEMDEV_NR_DEVS=2,分配2個設備
   
  /* 爲設備描述結構分配內存*/
  mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);//kmalloc函數返回的是虛擬地址(線性地址).
  if (!mem_devp)    /*申請失敗*/
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }
  memset(mem_devp, 0, MEMDEV_NR_DEVS * sizeof(struct mem_dev));//新申請的內存作初始化工做
  
  /*爲設備分配內存*/
  for (i=0; i < MEMDEV_NR_DEVS; i++) 
  {
        mem_devp[i].size = MEMDEV_SIZE;//#define MEMDEV_SIZE 4096
        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);//分配內存給兩個設備
        memset(mem_devp[i].data, 0, MEMDEV_SIZE);//初始化新分配到的內存
  }
    
  return 0;
  fail_malloc: 
  unregister_chrdev_region(devno, 1);//若是申請失敗,註銷設備
  
  return result;
}
/*模塊卸載函數*/
static void memdev_exit(void)
{
  cdev_del(&cdev);   /*註銷設備*/
  kfree(mem_devp);     /*釋放設備結構體內存*/
  unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/
}
MODULE_AUTHOR("cicue");
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);

Makefile

ifneq ($(KERNELRELEASE),)
    obj-m := memdev.o
else
 
KDIR := /forlinux/linux-3.0.1
all:
    make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-
clean:
    rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif

測試程序

#include <stdio.h>
int main()
{
 FILE *fp0 = NULL;
 char Buf[4096];
 
 /*初始化Buf*/
 strcpy(Buf,"Mem is char dev!");
 printf("BUF: %s\n",Buf);
 
 /*打開設備文件*/
 fp0 = fopen("/dev/memdev0","r+");
 if (fp0 == NULL)
 {
  printf("Open Memdev0 Error!\n");
  return -1;
 }
 
 /*寫入設備*/
 fwrite(Buf, sizeof(Buf), 1, fp0);
 
 /*從新定位文件位置(思考沒有該指令,會有何後果)*/
 fseek(fp0,0,SEEK_SET);//調用mem_llseek()定位
 
 /*清除Buf*/
 strcpy(Buf,"Buf is NULL!");
 printf("BUF: %s\n",Buf);
 
 
 /*讀出設備*/
 fread(Buf, sizeof(Buf), 1, fp0);
 
 /*檢測結果*/
 printf("BUF: %s\n",Buf);
 
 return 0; 
}

1,將編譯的.ko和app都下載到開發板

2,加載模塊.ko insmod memdev.ko

3,建立設備文件 手動建立過程 (動態分配時:設備號查看 cat /proc/devices 安裝設備就會在該目錄下出現相應的設備和設備號)

cd /dev

mknod  memdev0 c 190 0 (設備號要對應起來 運行這個後 會在/dev目錄下建立相應的文件)

4,讓後去運行app文件 ./memapp

相關文章
相關標籤/搜索