Linux核心模組程式設計之和裝置檔案對話(轉)

worldblog發表於2007-08-10
Linux核心模組程式設計之和裝置檔案對話(轉)[@more@]

  和裝置檔案對話(寫和 IOCTL)

  裝置檔案應該表現物理裝置。大多物理裝置既作為輸出也作為輸入,因此必須有某個機制使核心中的裝置驅動程式得到來自程式的輸出以便傳送到裝置。透過為輸出開啟裝置檔案並向其寫而做到這個,就像寫一個普通檔案。在下面的例子中,這是用 device_write 實現的。

  這不總是足夠的。想象你有一個序列口連線到一個調變解調器(即使你有一個內建的調變解調器,從CPU的觀點看它仍然是透過序列口連線到調變解調器,因此你不必責備你的想象力)。自然而然的事情是使用裝置檔案向調變解調器寫(要麼是調變解調器命令,要麼是要透過電話線傳送的資料)和從中讀(要麼是命令回應,要麼是接收的資料)。然而,這留下了當你需要和序列口對話時該做什麼的問題,例如以什麼速率接收和傳送資料。

  在 Unix 中,答案是使用特殊的函式呼叫 ioctl ( input output control 的縮寫)。每個裝置可以有自己的 ioctl 命令,它可以讀 ioctl (從程式向核心傳送資訊)和寫 ioctl(返回資訊給程式)(注意在這兒讀寫的作用又是顛倒的,因此ioctl 的讀是傳送訊息給核心而寫是從核心接收訊息)或者什麼也不做。 ioctl 使用三個引數呼叫: 合適的裝置檔案的檔案描述符, ioctl 號及一個引數,該引數是型別長度,因此你可以使用一個模型傳遞任何東西。 (這是不準確的。例如你不能透過ioctl傳遞一個結構 -- 但你可以傳遞那個結構的指標)

  ioctl 號用主裝置號, ioctl 型別,命令和引數型別編碼。這個 ioctl 號通常用一個標頭檔案中的宏呼叫 (_IO, _IOR, _IOW 或 _IOWR -- 取決於型別)建立。標頭檔案必須被使用ioctl的程式(因此它們可以生成合適的ioctl)及核心模組(因此它可以理解它) #include。 在下面的範例中,標頭檔案是 chardev.h 而使用它的程式是 ioctl.c。

  如果你想在你自己的模組中使用 ioctl ,最好接受官方的 ioctl 分配,因此如果你碰巧得到別人的ioctl或它們得到你的,你就可以知道某些事是錯的。需要更多資訊,請參考 `Documentation/ioctl-number.txt' 核心原始碼樹。

  範例 chardev.c

  /* chardev.c

*

* 建立輸入輸出的字元裝置

*/

/* Copyright (C) 1998-99 by Ori Pomerantz */

/* 必要標頭檔案 */

/* 標準標頭檔案 */

#include /* 核心工作 */

#include /* 明確指定是模組 */

/* 處理 CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include

#endif

/* 為了字元裝置 */

/* 字元裝置的定義在此 */

#include

/* 目前對後面妹妹有用的包裝,但可能對未來LINUX版本的相容性有幫助 */

#include

/* 我們自己的ioctl 號 */

#include "chardev.h"

/* 在 2.2.3 版/usr/include/linux/version.h 包含該宏,

* 但 2.0.35版不包含-加入以備需要 */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

#include /* 為了 get_user 和 put_user */

#endif

#define SUCCESS 0

/* 裝置宣告 ******************************** */

/* 將出現在 /proc/devices 中裝置名 */

#define DEVICE_NAME "char_dev"

/* 裝置的訊息的最大長度 */

#define BUF_LEN 80

/* 裝置正開啟?防止對同一裝置的同時訪問 */

static int Device_Open = 0;

/* 當被詢問時裝置將給出的訊息 */

static char Message[BUF_LEN];

/* 程式讀取訊息到哪兒?如果訊息的長度大於我們將用於填充在 device_read的緩衝區的大小,這將有用。*/

static char *Message_Ptr;

/* 這個函式在程式試圖開啟裝置檔案時被呼叫 */

static int device_open(struct inode *inode,

struct file *file)

{

#ifdef DEBUG

printk ("device_open(%p) ", file);

#endif

/* 我們不想同時和兩個程式對話 */

if (Device_Open)

return -EBUSY;

/* 如果這是個程式,我們將更小心,因為一個程式可能已經剛好在另一個程式試圖增加Device_Open

* 之前檢查過它。然而我們是在核心中,因此我們在上下文切換上被保護。

*

* 這不是我們應該採取的態度,因為我們可能執行在一個 SMP 單元上,但我們將在後面一章處理SMP

*/

Device_Open++;

/* 初始化訊息 */

Message_Ptr = Message;

MOD_INC_USE_COUNT;

return SUCCESS;

}

/* 當一個程式關閉裝置檔案時該函式被呼叫。它沒有返回值因為它不能失敗。不要考慮其他任何事的發生

* 你總應該可以關閉一個裝置(在 2.0 版中情況如此,在 2.2 版中裝置檔案可能不能關閉)。 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static int device_release(struct inode *inode,

struct file *file)

#else

static void device_release(struct inode *inode,

struct file *file)

#endif

{

#ifdef DEBUG

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

#endif

/* 為下個呼叫者做準備 */

Device_Open --;

MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

return 0;

#endif

}

/* 當一個已經開啟裝置檔案的程式試圖從它讀時該函式被呼叫。 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t device_read(

struct file *file,

char *buffer, /* 填充資料的緩衝區 */

size_t length, /* 緩衝區長度 */

loff_t *offset) /* 檔案偏移量 */

#else

static int device_read(

struct inode *inode,

struct file *file,

char *buffer, /* 填充資料的緩衝區 */

int length) /* 緩衝區長度(一定不能在寫時超過它!) */

#endif

{

/* 實際寫入緩衝區的位元組數 */

int bytes_read = 0;

#ifdef DEBUG

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

file, buffer, length);

#endif

/* 如果在訊息尾則返回0表示檔案尾 */

if (*Message_Ptr == 0)

return 0;

/* 實際上將資料放入緩衝區 */

while (length && *Message_Ptr) {

/* 因為緩衝區在使用者資料段而不是核心的資料段,分配無法工作。替代的,

* 我們使用將核心資料段中的資料複製到使用者資料段的 put_user 。*/

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

length --;

bytes_read ++;

}

#ifdef DEBUG

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

bytes_read, length);

#endif

/* 讀函式應該返回實際插入緩衝區的位元組數 */

return bytes_read;

}

/* 當有人向我們的裝置檔案寫時該函式被呼叫。 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t device_write(struct file *file,

const char *buffer,

size_t length,

loff_t *offset)

#else

static int device_write(struct inode *inode,

struct file *file,

const char *buffer,

int length)

#endif

{

int i;

#ifdef DEBUG

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

file, buffer, length);

#endif

for(i=0; i #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

get_user(Message[i], buffer+i);

#else

Message[i] = get_user(buffer+i);

#endif

Message_Ptr = Message;

/* 又一次返回使用過的輸入的位元組數 */

return i;

}

/* 當一個程式試圖在我們的裝置檔案上做 ioctl 時該函式被呼叫。我們需要兩個額外的引數

* (附加於節點結構和檔案結構,那是所有的裝置函式都需要的): ioctl 號和給出 ioctl 函式的引數

*

* 如果 ioctl 是寫或讀/寫(意味著輸出被返回給呼叫程式), ioctl 呼叫返回這個函式的輸出。

*/

int device_ioctl(

struct inode *inode,

struct file *file,

unsigned int ioctl_num,/* ioctl 號 */

unsigned long ioctl_param) /* 對它的引數 */

{

int i;

char *temp;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

char ch;

#endif

/* 根據 ioctl 呼叫選擇 */

switch (ioctl_num) {

case IOCTL_SET_MSG:

/* 接收指向訊息的指標(在使用者空間)並將它設為裝置的訊息 */

/* 得到由程式給出的給 ioctl 的引數 */

temp = (char *) ioctl_param;

/* 找到訊息的長度 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

get_user(ch, temp);

for (i=0; ch && i get_user(ch, temp);

#else

for (i=0; get_user(temp) && i ;

#endif

/* 不要重新發明車輪-呼叫 device_write */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

device_write(file, (char *) ioctl_param, i, 0);

#else

device_write(inode, file, (char *) ioctl_param, i);

#endif

break;

case IOCTL_GET_MSG:

/* 將當前的訊息給呼叫程式 - 引數是一個指標,填充它 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

i = device_read(file, (char *) ioctl_param, 99, 0);

#else

i = device_read(inode, file, (char *) ioctl_param,

99);

#endif

/* 警告 - 我們假設緩衝區長度是100。如果它小於那將使緩衝區溢位而導致程式傾倒核心

*(生成core檔案)。

*

* 我們只允許99個字元的原因是字串終止符 NULL 也需要空間。 */

/* 將0放置在緩衝區尾使它適當的終止。 */

put_user('', (char *) ioctl_param+i);

break;

case IOCTL_GET_NTH_BYTE:

/* 這個 ioctl 既輸入 (ioctl_param) 也輸出(這個函式的返回值) */

return Message[ioctl_param];

break;

}

return SUCCESS;

}

/* 模組宣告 *************************** */

/* 這個結構將儲存當程式對我們建立的裝置做什麼時將呼叫的函式。因為這個結構的指標被儲存在

* 裝置表中,所以它不能對 init_module是區域性的。 NULL 是為未實現的函式保留的。 */

struct file_operations Fops = {

NULL, /* 偏移 */

device_read,

device_write,

NULL, /* 讀目錄 */

NULL, /* 選擇 */

device_ioctl, /* ioctl */

NULL, /* mmap */

device_open,

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

NULL, /* 重新整理 */

#endif

device_release /* 又名關閉 */

};

/* 初始化模組 - 登記字元裝置 */

int init_module()

{

int ret_val;

/* 登記字元裝置(至少是試圖登記) */

ret_val = module_register_chrdev(MAJOR_NUM,

DEVICE_NAME,

&Fops);

/* 負數表示錯誤 */

if (ret_val < 0) {

printk ("%s failed with %d ",

"Sorry, registering the character device ",

ret_val);

return ret_val;

}

printk ("%s The major device number is %d. ",

"Registeration is a success",

MAJOR_NUM);

printk ("If you want to talk to the device driver, ");

printk ("you'll have to create a device file. ");

printk ("We suggest you use: ");

printk ("mknod %s c %d 0 ", DEVICE_FILE_NAME,

MAJOR_NUM);

printk ("The device file name is important, because ");

printk ("the ioctl program assumes that's the ");

printk ("file you'll use. ");

return 0;

}

/* 清除 - 從 /proc 中登出相關檔案 */

void cleanup_module()

{

int ret;

/* 登出裝置 */

ret = module_unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

/* 如果有錯誤,報告它 */

if (ret < 0)

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

}

範例 chardev.h

/* chardev.h - 包含 ioctl 定義的標頭檔案。

*

* 這裡的宣告必須在標頭檔案中,因為他們需要被核心模組(chardev.c)和呼叫它的程式 (ioctl.c)知道。

*/

#ifndef CHARDEV_H

#define CHARDEV_H

#include

/* 主裝置號。我們不能再依賴於動態的登記,因為 ioctl 需要知道它。 */

#define MAJOR_NUM 100

/* 設定裝置驅動程式的訊息 */

#define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *)

/* _IOR 意思是我們正在為從使用者程式到核心模組的資訊建立一個 ioctl 命令號。

*

* 第一個引數, MAJOR_NUM,是我們使用的主裝置號。

*

* 第二個引數是命令號(可能是幾個帶有不同的意思的)。

*

* 第三個引數是我們想得到的從程式傳到核心的型別。

*/

/* 得到裝置驅動程式的訊息 */

#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)

/* 這個 IOCTL 用於輸出,得到裝置驅動程式的訊息。然而我們仍然需要緩衝區作為輸入放置訊息,因為

* 它已經被程式分配了。

*/

/* 得到訊息的第n個位元組 */

#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int)

/* 這個 IOCTL 既用於輸入也用於輸出。它接受來自使用者的一個數 n ,然後返回 Message[n]。 */

/* 裝置檔名 */

#define DEVICE_FILE_NAME "char_dev"

#endif

範例 ioctl.c

/* ioctl.c - 程式使用 ioctl 去控制核心模組

*

* 直到現在我們都使用 cat 做輸入和輸出。但是現在我們需要做 ioctl ,這需要寫自己的程式。

*/

/* Copyright (C) 1998 by Ori Pomerantz */

/* 裝置細節,例如 ioctl號和主裝置號 */

#include "chardev.h"

#include /* 開啟 */

#include /* 退出 */

#include /* ioctl */

/* ioctl 呼叫函式 */

ioctl_set_msg(int file_desc, char *message)

{

int ret_val;

ret_val = ioctl(file_desc, IOCTL_SET_MSG, message);

if (ret_val < 0) {

printf ("ioctl_set_msg failed:%d ", ret_val);

exit(-1);

}

}

ioctl_get_msg(int file_desc)

{

int ret_val;

char message[100];

/* 警告 - 這是危險的,因為我們沒有告訴核心它允許寫多遠,因此它可能使緩衝區溢位。

* 在真正的產品程式中,我們應該使用兩個 ioctl - 一個告訴核心緩衝區長度而另一個填充緩衝區

*/

ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);

if (ret_val < 0) {

printf ("ioctl_get_msg failed:%d ", ret_val);

exit(-1);

}

printf("get_msg message:%s ", message);

}

ioctl_get_nth_byte(int file_desc)

{

int i;

char c;

printf("get_nth_byte message:");

i = 0;

while (c != 0) {

c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++);

if (c < 0) {

printf(

"ioctl_get_nth_byte failed at the %d'th byte: ", i);

exit(-1);

}

putchar(c);

}

putchar(' ');

}

/* 主函式 - 呼叫 ioctl 函式 */

main()

{

int file_desc, ret_val;

char *msg = "Message passed by ioctl ";

file_desc = open(DEVICE_FILE_NAME, 0);

if (file_desc < 0) {

printf ("Can't open device file: %s ",

DEVICE_FILE_NAME);

exit(-1);

}

ioctl_get_nth_byte(file_desc);

ioctl_get_msg(file_desc);

ioctl_set_msg(file_desc, msg);

close(file_desc);

}

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-940226/,如需轉載,請註明出處,否則將追究法律責任。

相關文章