主題:《Linux核心模組編程指南》(二)

來源:互聯網
上載者:User
主題:《Linux核心模組編程指南》(二)

發信人: kevintz()
整理人: kevintz(2000-06-22 00:59:18), 站內信件

<<Linux 核心模組編程指南>>

<<Linux Kernel Module Programming Guide>>

作者:Ori Pomerantz 中譯者:譚志(lkmpg@21cn.com)

  

譯者註:

1、LKMPG是一本免費的書,英文版的發行和修改遵從GPL version 2的許可。為了
節省時間,我只翻譯了其中的大部分的大意,或者說這隻是我學習中的一些中文
筆記吧,不能算是嚴格上的翻譯,但我認為這已經足夠了。本文也允許免費發布
,但發布前請和我聯絡,但不要把本文用於商業目的。鑒於本人的水平,文章中
難免有錯誤,請大家不吝指正。

2、本文中的例子在Linux(kernel version 2.2.10)上調試通過。你用的Linux必
須支援核心模組的載入,如果不支援,請在編譯核心時選上核心模組的支援或升
級你的核心到一個支援核心模組的版本。

     

                          第二章 字元裝置檔案

    我們已經寫了最簡單的核心模組,並且它的確能正常運行。但我們好象還缺
少點什麼,畢竟這麼簡單的核心模組不是很能引起我們的熱情。

    核心模組有兩個主要方法跟進程進行通訊。一個是通過裝置檔案(象/dev目錄
下的檔案),另一個是使用proc檔案系統。當我們寫驅動程式來支援一些硬體時,
通常是使用裝置檔案,因此,我們從裝置檔案開始。

      

    裝置檔案原來的目的是允許進程和核心中的裝置驅動程式通訊,再通過驅動
程式和物理裝置(modem,終端等)進行通訊。這就是用來實現我們下面的字元核心
模組的方式。

    

    每個負責管理相應硬體的裝置驅動程式都被賦予一個它自己的主裝置號(maj
or number)。系統中可用驅動程式的列表和他們的主裝置號被列出在/proc/devi
ces檔案裡。被裝置驅動程式管理的每一個物理裝置都被賦予一個次裝置號,並在
/dev目錄下有一個對應的檔案,叫做裝置檔案,但不論真實物理裝置是否安裝,
它都會在/dev下有這個檔案。

    例如,如果你用ls -l /dev/hd[ab]*,你會看到所有連到機器的IDE硬碟的分
區。它們都用同一個主裝置號,但次裝置號卻不同(假設你是使用PC體系)。我不
清楚運行在其他體系的機器上的Linux的裝置情況。

    系統安裝時,所有這些裝置檔案通過mknod命令產生。並沒有技術上的原因使
得這些檔案一定要放在/dev目錄下,僅僅是一個有用的慣例而已。當我們的例子
要建立一個裝置檔案時,放在編譯核心模組的目錄下比放在/dev下更方便些。

    

    裝置分為兩類:字元裝置和塊裝置。塊裝置的不同之處在於對請求有緩衝機
制,所以能夠以不同於請求的順序來回應請求。這對存放裝置這些快速的裝置很
重要。另一個不同之處是塊裝置只能以塊(塊的大小隨不同的裝置而不同)為單位
來讀寫資料。字元裝置則允許讀寫任意數量的位元組,多少只由進程的需要來決定
。大多數的裝置都是字元裝置,因為他們不需要緩衝機制和操作不要求以塊為限
制。你可以用ls -l命令來判斷一個裝置檔案是塊裝置還是字元裝置,第一個字元
是'b'表示是塊裝置,為'c'則表示字元裝置。

    本章的核心模組例子分為兩個部分:註冊裝置的模組和裝置驅動模組。init
_module函數調用module_register_chrdev來把裝置驅動程式加入到核心的字元設
備驅動程式表中,並返回驅動程式所使用的主裝置號。cleanup_module函數登出
驅動程式。

    這(註冊和登出驅動程式)是這兩個函數最常用的功能。核心裡的東西都不會
主動運行,通常都是被調用,例如進程通過系統調用、硬體通過中斷或核心其他
部分的調用來運行核心中的函數。所以當你向核心裡添加代碼,你要作為一些事
件的處理函數來註冊它,當你去掉它時,你要登出它。

    裝置驅動程式部分由四個device_<action>函數組成,當使用者嘗試對屬於我們
的主裝置號的裝置檔案做一些動作時,這些函數將被調用。核心調用它們的方法
是通過file_operations結構體--Fops。它包含了四個函數的的指標,並在註冊設
備驅動程式時用到。

    在這裡我們有一點要記住,我們不能使核心模組被root使用者在任何時候都能
rmmod。因為一個進程正開啟一個裝置檔案的時侯我們移掉一個核心模組,對裝置
檔案的訪問會引起對原來驅動程式中相應的讀寫函數的記憶體位置的訪問。如果幸
運的話,沒有其他代碼被載入到那裡,我們將會得到糟糕的錯誤資訊。如果不幸
運的話,另一個核心模組被載入到相同的位置,這意味著跳到核心的另一個完全
不同的函數裡執行。結果將是不可預知的,但這種錯誤卻又是不明顯的。

    通常,你不想允許某些事情時,你可以在函數中返回一個錯誤碼(一個負數)
。但cleanup_module函數卻不能,因為它是聲明為void型的函數。一旦clean_mo
dule被調用,模組就死掉了。但是可以用一個稱為參考計數器(檔案/proc/modul
es裡的最後一行的數字)來表示有多少個其他核心模組使用了當前模組。如果這個
數字不為0,rmmod將會失敗。模組的參考計數器是用變數mod_use_count_表示。
有兩個宏定義(MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT)來處理這個變數,我們
不應直接使用mod_use_count_,應該用宏來處理,這使我們的代碼在以後實現改
變的時候,仍然是安全的。

例子chardev.c    

 

/* chardev.c 

 * Copyright (C) 1998 by Ori Pomerantz

 * 

 * Create a character device (read only)

 */

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h>   /* We're doing kernel work */

#include <linux/module.h>   /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif        

/* For character devices */

/* The character device definitions are here */

#include <linux/fs.h> 

/* A wrapper which does next to nothing at

 * at present, but may help for compatibility

 * with future versions of Linux */

#include <linux/wrapper.h>       

#define SUCCESS 0

/* The name for our device, as it will appear in /proc/devices */

#define DEVICE_NAME "char_dev"

/* The maximum length of the message from the device */

#define BUF_LEN 80

/* 裝置是否被開啟的標誌?用來防止對同一裝置的並發訪問 */

static int Device_Open = 0;

static char Message[BUF_LEN];

/*該指標用於標識資訊的位置,當使用者進程讀操作時,使用者緩衝區比Message小

  就要用到*/

static char *Message_Ptr;

/* 本函數在使用者進程開啟裝置檔案時被調用*/

static int device_open(struct inode *inode, struct file *file)

{

  static int counter = 0;

#ifdef DEBUG

  printk ("device_open(%p,%p)/n", inode, file);

#endif

  /* 當你有多個物理裝置都用這個驅動程式時,這裡是取得次裝置號的方法*/

  printk("Device: %d.%d/n", inode->i_rdev >> 8, inode->i_rdev & 0xFF);

  /* 現時,我們不想同時和多個使用者進程通訊*/

  if (Device_Open)

    return -EBUSY;

  /* 這裡可能潛在一個錯誤,當一個進程得到Device_Open值為0,而在增加

     該值時被停止調度,另一進程也開啟裝置檔案,並增加了Device_Open的

     值,這時,第一個進程又再運行,則以為Device_Open還是為0,所以是

     錯誤的。

     但你不用擔心,Linux的核心保證一個進程在運行核心的代碼時是不會被

     搶佔的,所以上面的情況可以避免。

     在SMP的情形,2.0核心通過加鎖來保證在同一時候只有一個CPU在核心模組

     裡運行。這影響了效能,這應該在以後的核心版本得以安全地修正。 */

  Device_Open++;

  /* Initialize the message. */

  sprintf(Message, 

    "If I told you once, I told you %d times - Hello, world/n",

    counter++);

  /* 這裡要注意緩衝區溢位,特別是在核心模組裡  */ 

  Message_Ptr = Message;

  /* 保證裝置檔案被開啟時,核心模組不能被登出掉(通過增加計數器)

     如果計數器非零,rmmod將失敗*/

  MOD_INC_USE_COUNT;

  return SUCCESS;

}

/* 當裝置檔案被關閉時,調用本函數。它不返回錯誤,因為你要保證通常

   都能關閉一個裝置*/

static void device_release(struct inode *inode, struct file *file)

{

#ifdef DEBUG

  printk ("device_release(%p,%p)/n", inode, file);

#endif

 

  /* We're now ready for our next caller */

  Device_Open --;

  /*減少計數器*/

  MOD_DEC_USE_COUNT;

}

/* 進程讀一個開啟的裝置檔案時調用本函數*/

static int device_read(struct inode *inode,

                       struct file *file,

                       char *buffer,  

                       /* 接收資料的緩衝區和長度*/ 

                       int length) 

{

  /* Number of bytes actually written to the buffer */

  int bytes_read = 0;

#ifdef DEBUG

  printk("device_read(%p,%p,%p,%d)/n",

    inode, file, buffer, length);

#endif

  /* If we're at the end of the message, return 0 */

  if (*Message_Ptr == 0)

    return 0; /*it means end of file */

  /* Actually put the data into the buffer */

  while (length && *Message_Ptr)  {

    /*由於緩衝區在使用者資料區段,不在核心空間,所以不能通過賦值的方式

      來拷貝資料,應通過put_user調用來傳輸從核心到使用者空間的資料*/ 

    put_user(*(Message_Ptr++), buffer++);

    length --;

    bytes_read ++;

  }

#ifdef DEBUG

   printk ("Read %d bytes, %d left/n",

     bytes_read, length);

#endif

   /* 返回所讀的位元組數*/

  return bytes_read;

}

/* 寫裝置檔案時調用的函數,當前不支援,返回-EINVAL碼*/

static int device_write(struct inode *inode,

                        struct file *file,

                        const char *buffer,

                        int length)

{

#ifdef DEBUG

  printk ("device_write(%p,%p,%s,%d)",

    inode, file, buffer, length);

#endif

  return -EINVAL;

}

/* 主裝置號,聲明為靜態是因為註冊和登出都要用到它*/

static int Major;

/* 裝置檔案操作的結構體*/

struct file_operations Fops = {

  NULL,   /* seek */

  device_read, 

  device_write,

  NULL,   /* readdir */

  NULL,   /* select */

  NULL,   /* ioctl */

  NULL,   /* mmap */

  device_open,

  device_release  /* a.k.a. close */

};

/* Initialize the module - Register the character device */

int init_module()

{

  /* Register the character device (at least try) */

  Major = module_register_chrdev(0, 

                                 DEVICE_NAME,

                                 &Fops);

  /* Negative values signify an error */

  if (Major < 0) {

printk ("Sorry, registering the character device failed with %d/n"
,

Major);

return Major;

}

printk ("Registeration is a success. The major device number is %d./
n",

Major);

printk ("If you want to talk to the device driver, you'll have to/n"
);

printk ("create a device file. We suggest you use:/n");

printk ("mknod <name> c %d <minor>/n", Major);

  printk ("You can try different minor numbes and see what happens./n"
);

  return 0;

}

/* Cleanup - unregister the appropriate file from /proc */

void cleanup_module()

{

  int ret;

  /* Unregister the device */

  ret = module_unregister_chrdev(Major, DEVICE_NAME);

 

  /* If there's an error, report it */ 

  if (ret < 0)

printk("Error in module_unregister_chrdev: %d/n", ret);

}

譯者註:這個核心模組有很多錯誤和編譯上要注意的問題,所以我自己特別寫了
一章來解決這些問題。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.