簡單驅(qū)動word_count

目的:寫一個統(tǒng)計單詞個數(shù)的簡單驅(qū)動。

1、驅(qū)動的概念

1.1、什么是驅(qū)動

無操作系統(tǒng)時函數(shù)可以由工程師自己定義。
有操作系統(tǒng)時,把單一的“驅(qū)使硬件設(shè)備行動”變成了操作系統(tǒng)內(nèi)與硬件交互的模塊,它對外呈現(xiàn)為操作系統(tǒng)的 API。
對設(shè)備驅(qū)動最通俗的解釋就是“驅(qū)使硬件設(shè)備行動”。設(shè)備驅(qū)動充當了硬件和應(yīng)用軟件之間的紐帶,應(yīng)用軟件時只需要調(diào)用系統(tǒng)軟件的應(yīng)用編程接口( API)就可讓硬件去完成要求的工作。

1.2 為什么要有驅(qū)動

任何一個計算機系統(tǒng)的運轉(zhuǎn)都是系統(tǒng)中軟硬件共同努力的結(jié)果,但是軟硬件工程師不想涉及對方的領(lǐng)域,因此這個中間的連接需要驅(qū)動工程師來解決。

1.3 驅(qū)動所在位置

linux設(shè)備驅(qū)動是以內(nèi)核模塊的形式出現(xiàn)。模塊的概念,可以不編入內(nèi)核鏡像,在使用的時候動態(tài)的加入到內(nèi)核中,模塊被加載后和其他內(nèi)核完全一樣。


image.png
image.png

2、linux字符設(shè)備驅(qū)動結(jié)構(gòu)

2.1 重要的兩個結(jié)構(gòu)體

2.1.1 cdev結(jié)構(gòu)體

linux內(nèi)核中,使用cdev(character dev)結(jié)構(gòu)體描述一個字符設(shè)備,結(jié)構(gòu)體如下:

image.png

  1. dev_t:定義了設(shè)備號,為32位,12位為主設(shè)備號,20位為次設(shè)備號。
    使用MAJOR(dev_t dev)、MINOR(dev_t dev)可以獲得,使用MKDEV(int major, int minor)可以通過主設(shè)備號和次設(shè)備號生成dev_t:

2.1.2 file_operations 結(jié)構(gòu)體

file_operations:定義了字符設(shè)備驅(qū)動提供給VFS的接口函數(shù),是 字符設(shè)備驅(qū)動程序設(shè)計的主題內(nèi)容,會在應(yīng)用程序進行l(wèi)inus的open/read()等系統(tǒng)調(diào)用時最終被內(nèi)核調(diào)用

struct file_operations {
        **struct module *owner;**
//指向module,一般初始化為THIS_MODULE
        loff_t (*llseek) (struct file *, loff_t, int);
//修改一個文件的當前讀寫位置,并將新位置返回;指針參數(shù)filp為進行讀取信息的目標文件結(jié)構(gòu)體指針,
//loff_t用來維護當前讀寫位置,代表用戶空間的文件光標移動數(shù)量值
//int 代表移動光標的參考位置,有3種(即當前光標、文件開頭、文件結(jié)尾)
//移動設(shè)備的文件指針,然后讀/寫接口就可以對移動后的位置進行讀取功能,而不是每次讀/寫都只能從0開始一次讀取全部數(shù)據(jù)。llseek函數(shù)改變了filp->f_pos(position),返回新的位置
//實現(xiàn)的功能是修改驅(qū)動中write/read函數(shù)的文件指針。
        **ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);**
// 從設(shè)備中讀取數(shù)據(jù),成功時返回讀取字節(jié)數(shù)
//PS:這里是_ _user
       **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);
//對文件描述符對應(yīng)的設(shè)備進行異步讀操作。
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
//對于設(shè)備文件這個成員應(yīng)當為 NULL; 它用來讀取目錄, 并且僅對文件系統(tǒng)有用.
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
//返回設(shè)備資源的可獲取狀態(tài),詢問是否可被非阻塞地立即讀寫。
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
// 提供設(shè)備相關(guān)控制命令的實現(xiàn)。
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
// 
        int (*mmap) (struct file *, struct vm_area_struct *);
// 將設(shè)備內(nèi)存映射到進程的虛擬地址空間,對幀緩沖等設(shè)備特別有意義,
//幀緩沖被映射到用戶空間后,應(yīng)用程序可以直接訪問無需在內(nèi)核和應(yīng)用間進行內(nèi)存復(fù)制。
//幀緩沖是Linux為顯示設(shè)備提供的一個接口,幀緩存的每一存儲單元對應(yīng)屏幕上的一個像素,整個幀緩存對應(yīng)一幀圖像。
      ** int (*open) (struct inode *, struct file *);**
//打開設(shè)備驅(qū)動
//inode 為文件節(jié)點,這個節(jié)點只有一個,無論用戶打開多少個文件,都只是對應(yīng)著一個inode結(jié)構(gòu);
//filp就不同,只要打開一個文件,就對應(yīng)著一個file結(jié)構(gòu)體,file結(jié)構(gòu)體通常用來追蹤文件在運行時的狀態(tài)信息
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, 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 **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
        int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

2.2 linux字符設(shè)備驅(qū)動的組成

  1. 字符設(shè)備驅(qū)動模塊加載與卸載函數(shù)
    加載函數(shù):設(shè)備號的申請和cdev的注冊
    卸載函數(shù):設(shè)備號的釋放和cdev的注銷
    image.png

memset是計算機中C/C++語言初始化函數(shù)。作用是將某一塊內(nèi)存中的內(nèi)容全部設(shè)置為指定的值, 這個函數(shù)通常為新申請的內(nèi)存做初始化工作。extern void *memset(void *buffer, int c, int count) buffer:為指針或是數(shù)組, c:是賦給buffer的值, count:是buffer的長度。
cdev_add 和cdev_del分別向系統(tǒng)添加和刪除一個cdev,完成字符設(shè)備的注冊和注銷。

struct xxx_dev_t{
  struct cdev cdev;
  ...
} xxx_dev;
static int _ _init xxx_init(void)
{
  cdev_init(&xxx_dev.cdev, &xxx_fops);
  xxx_dev.cdev.owner = THIS_MODULE;
  ret = cdev_add(&xxx_dev.cdev,xxx_dev_no,1);

static void _ _exit xxx_exit(void)
{
  unregister_chrdev_region{xxx_dev_no,1);
//釋放設(shè)備號
  cdev_del{&xxx_dev.cdev};
}
  1. 字符設(shè)備驅(qū)動的file_operations結(jié)構(gòu)體的成員函數(shù)
    file_operations中的成員函數(shù)是字符設(shè)備驅(qū)動與內(nèi)核虛擬文件系統(tǒng)的接口,是用戶空間對linux進行系統(tǒng)調(diào)用的最終落實者。以read()為例
ssize_t xxx_read(struct file *filp, char _ _ user *buf, size_t count, loff_t *f_pos)
{
  ...
  copy_to_user(buff,...,...);
}

這里:完成內(nèi)核空間和用戶空間內(nèi)存復(fù)制的是
copy_from_user() 、copy_to_user()
完全復(fù)制成功,返回值為0.

image.png

3、編寫一個驅(qū)動

3.1 編寫linux驅(qū)動程序的步驟

  1. 宏定義與頭文件
  2. 注冊和注銷設(shè)備文件
  3. 指定與驅(qū)動相關(guān)的信息
  4. 指定回調(diào)函數(shù)
  5. 編寫業(yè)務(wù)邏輯
  6. 編寫Makefile文件

經(jīng)過上面的6個步驟,可以安裝和卸載linux驅(qū)動

3.2 編寫word_count驅(qū)動

3.2.1 宏定義

#define DEVICE_NAME "wordcount"
#define DEVICE_COUNT 1
#define word_count_MAJOR 0
#define word_count_MINOR 234
#define TRUE -1
#define FALSE 0
static unsigned char mem[10000]
static int major = word_count_MAJOR;
static int minor = word_count_MAJOR;
static dev_t dev_number;

3.2.3 注冊和注銷設(shè)備文件

//
// 定義file_operations結(jié)構(gòu)體
 static struct file_operations dev_fops =
{.owner = THIS_MODULE, .read = word_count_read, .write = word_count_write};

// 定義cdev結(jié)構(gòu)體,用于描述字符設(shè)備
static struct cdev word_count_cdev;
//
//
// 創(chuàng)建設(shè)備文件
static int word_count_create_device(void)
{
  int ret = 0;
  int err = 0;

//初始化cdev的成員,并建立cdev和file_operations之間的連接
  cdev_init(&word_count_cdev,&dev_fops);
  wordcount_cdev.owner = THIS_MODULE;
if (major >0)
{
// MKDEV:可通過主設(shè)備號和次設(shè)備號生成dev_t
  dev_number = MKDEV(major,minor);
// #define DEVICE_NAME "wordcount"
// #define DEVICE_COUNT 1
// 注冊字符設(shè)備區(qū)
  err = register_chrdev_region(dev_number,DEVICE_COUNT,DEVICE_NAME);
  if (err < 0) 
  {   printk(kern_warning " register_chrdev_region() failed\n");
    retun err;
  }
else
  {
//隨機分配設(shè)備號,并注冊字符設(shè)備區(qū)
    err = alloc_chrdev_region(&word_count_cdev.dev,10,DEVICE_COUNT,DEVICE_NAME);
  if (err < 0) 
  {   printk(kern_warning " register_chrdev_region() failed\n");
      retun err;
  }
  major = MAJOR(word_count_cdev.dev);
  minor = MINOR(word_count_cdev.dev);
  dev_number = word_count_cdev.dev;
}
//添加字符設(shè)備
ret = cdev_add(&word_count_cdev,dev_number,DEVICE_COUNT);
word_count_class = class_create(TIHIS_MODULE,DEVICE_NAME);
//創(chuàng)建設(shè)備文件
device_create(word_count_class,NULLndev_number,DEVICE_NAME);
return ret;
}

// 銷毀字符設(shè)備
static void word_count_destory_decice(void)
{
 // 銷毀字符設(shè)備
device_destroy(word_count_class,dev_number);
// 銷毀class 結(jié)構(gòu)體
if (word_count_class)
  class_destroy(word_count_class);
// 注銷字符設(shè)備區(qū)
unregister_chrdev_region(dev_number, DEVICE_COUNT);
return;
}


// 初始化linux wordcount驅(qū)動
static int word_count_init(void)
{
  int ret;
  ret  = word_count_create_device();
  return ret;
}
// 卸載wordcount 驅(qū)動
static void word_count_exit(void)
{
  word_count_destroy_device();
}

//指定word_count驅(qū)動的初始化和卸載
module_init(word_count_init);
module_exit(word_count_exit);

3.2.4 指定與驅(qū)動相關(guān)的信息

 MODULE_AUTHOR("GUQUAN");
 MODULE_DESCRIPTION("statistics of word count. ");
 MODULE_ALIAS("word count module. ");
 MODULE_LICENSE("GPL ");

3.2.5 編寫業(yè)務(wù)邏輯

 static char is_spacewhite(char c)
 {
      if(c ==  ' ' || c == 9 || c == 13 || c == 10)
          return TRUE;
     else
         return FALSE;
 }
//空格、回車、換行分割的字符串作為一個單詞
 static int get_word_count(const char *buf)
{
     int n = 1;
     int i = 0;
     char c = ' ';
     char flag  = 0;// 處理多個空格情況:0:正常情況,1:已遇到一個空格
     if(*buf == '\0')
         return 0;
//第一個字符是空格,從0開始計數(shù)
      if(is_spacewhite(*buf) == TRUE)
          n--;
//掃描字符串中的每一個字符,假設(shè):hello world\0
////空格、回車、換行分割的字符串作為一個單詞
      for(;(c = *(buf + i)) != '\0'; i++)
      {
          if(flag == 1 && is_spacewhite(c) == FALSE)
                      flag = 0;
//多個空格時,忽略
          else if(flag == 1 && is_spacewhite(c) == TRUE)
                      continue; 

          if(is_spacewhite(c) == TRUE)
          {
              n++;
             flag = 1;
         }
      }
//如果字符串以一個或多個空格結(jié)尾,不計數(shù)
      if(is_spacewhite(*(buf + i - 1)) == TRUE)
          n--;
      return n;
  }

 //從設(shè)備文件讀取數(shù)據(jù)
  static ssize_t word_count_read(struct file *file, char *buf, size_t count, loff_t *ppos)
  {
      unsigned char temp[4];
// 將單詞數(shù)(int類型)分解成4個字節(jié)存儲在buf中
      temp[0] =word_count >> 24;
      temp[1] =word_count >> 16;
      temp[2] =word_count >> 8;
      temp[3] =word_count;
      copy_to_user(buf,(void*) temp,4);
      printk(" read:word count:%d",(int) count);
      return count;
  }
 
  //向設(shè)備文件寫入數(shù)據(jù)
static ssize_t word_count_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
 {

         ssize_t written = count;
         copy_from_user(mem,buf,count);
          mem[count] = '\0';
          
         word_count = get_word_count(mem);
          
         printk("writen:word count :%d",(int)word_count);
         return written;
 }

3.2.6 編寫Makefile文件

obj-m += word_count.o

3.2.7 安裝和卸載word_count驅(qū)動

安裝:insmod word_count.ko
卸載:rmmod word_count

3.2.8 test_main

  7 int main(int argc,char *argv[])
  8 {
  9      int testdev;
 10     unsigned char buf[4];
 11     testdev =  open("/dev/wordcount",O_RDWR);
 12
 13     if(testdev == -1)
 14     {
 15         printf("cann't open file,because cann't open /dev \n");
 16         return 0;
 17     }
 18     if(argc >1)
 19     {
 20         write(testdev,argv[1],strlen(argv[1]));
 21         printf("string:%s\n", argv[1]);
 22     }
 23
 24     read(testdev,buf,4);
 25
 26     int n = 0;
 27     n = ((int) buf[0]) << 24 | ((int) buf[1]) << 16 | ((int) buf[2]) << 8 | ((int) buf[3]);
 28     printf("word byte display :%d,%d,%d,%d\n", buf[0],buf[1],buf[2],buf[3]);
 29
 30     printf("word count : %d\n",n);
 31     close(testdev);
 32     return 0;
 33 }

4、遇到的問題

  1. 掛載:insmod
    提示:operation not permitted
    解決使用sudo insmod word_count.ko
  2. ./test_word_count時,提示cann't open file,然后使用命令查看權(quán)限:ll /dev
    可以看到如下信息:
    crw --- --- 1 root root 10, 58 8月 13 16:33 wordcount
    image.png

文件類型
linux一共由7種文件類型,分別如下:
-:普通文件
d:目錄文件
l:軟鏈接(類型Windows的快捷方式)
b:塊設(shè)備文件(硬盤、光驅(qū))
p:管道文件
c:字符設(shè)備文件
s:套接口文件/數(shù)據(jù)接口文件(例如啟動Mysql服務(wù)器時產(chǎn)生的mysql.sock文件)

所有者(u表示)權(quán)限,組群(group),其他人(other)
文件權(quán)限:
| r(read) | 4 | 可讀 |
| w(write) | 2 | 可寫 |
| x(execute) | 1 | 可執(zhí)行 |

sudo chmod 777 /dev/wordcount,然后查看,crwxrwxrwx
然后執(zhí)行 ./test_word_count “hello world",可以得到如下結(jié)果


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容