Linux核心程式設計(字元裝置檔案)(轉)

BSDLite發表於2007-08-12
Linux核心程式設計(字元裝置檔案)(轉)[@more@]那麼,現在我們是原始級的核心程式設計師,我們知道如何寫不做任何事情的核心模組。我們為自己而驕傲並且高昂起頭來。但是不知何故我們感覺到缺了什麼東西。患有精神緊張症的模組不是那麼有意義。
核心模組同程式對話有兩種主要途徑。一種是透過裝置檔案(比如/dev 目錄中的檔案),另一種是使用proc檔案系統。我們把一些東西寫入核心的一個主要原因就是支援一些硬體裝置,所以我們從裝置檔案開始。
裝置檔案的最初目的是允許程式同核心中的裝置驅動通訊,並且透過它們和物理裝置通訊(modem,終端,等等)。這種方法的實現如下:
每個裝置驅動都對應著一定型別的硬體裝置,並且被賦予一個主碼。裝置驅動的列表和它們的主碼可以在in/proc/devices中找到。每個裝置驅動管理下的物理裝置也被賦予一個從碼。無論這些裝置是否真的安裝,在/dev目錄中都將有一個檔案,稱作裝置檔案,對應著每一個裝置。
例如,如果你進行ls –l /dev/hd[ab] *操作,你將看見可能聯結到某臺機器上的所有的IDE硬碟分割槽。注意它們都使用了同一個主碼,3,但是從碼卻互不相同。(宣告:這是在PC結構上的情況,我不知道在其他結構上執行的linux是否如此。)
在系統安裝時,所有裝置檔案在mknod命令下被建立。它們必須建立在/dev目錄下沒有技術上的原因,只是一種使用上的便利。如果是為測試目的而建立的裝置檔案,比如我們這裡的練習,可能放在你編譯核心模組的的目錄下更加合適。
裝置可以被分成兩類:字元裝置和塊裝置。它們的區別是塊裝置有一個用於請求的緩衝區,所以它們可以選擇用什麼樣的順序來響應它們。這對於儲存裝置是非常重要的,讀取相鄰的扇區比互相遠離的分割槽速度會快得多。另一個區別是塊裝置只能按塊(塊大小對應不同裝置而變化)接受輸入和返回輸出,而字元裝置卻按照它們能接受的最少位元組塊來接受輸入。大部分裝置是字元裝置,因為它們不需要這種型別的緩衝。你可以透過觀看ls -l命令的輸出中的第一個字元而知道一個裝置檔案是塊裝置還是字元裝置。如果是b就是塊裝置,如果是c就是字元裝置。
這個模組可以被分成兩部分:模組部分和裝置及裝置驅動部分。Init_module函式呼叫module_register_chrdev在核心得塊裝置表裡增加裝置驅動。同時返回該驅動所使用的主碼。Cleanup_module函式撤銷裝置的註冊。
這些操作(註冊和登出)是這兩個函式的主要功能。核心中的函式不是象程式一樣自發執行的,而是透過系統呼叫,或硬體中斷或者核心中的其它部分(只要是呼叫具體的函式)被程式呼叫的。所以,當你向內和中增加程式碼時,你應該把它註冊為具體某種事件的控制程式碼,而當你把它刪除的時候,你需要登出這個控制程式碼。
裝置驅動完全由四個裝置_
另一點我們需要記住的是,我們不能允許管理員隨心所欲的刪除核心模組。這是因為如果裝置檔案是被程式開啟的,那麼我們刪除核心模組的時候,要使用這些檔案就會導致訪問正常的函式(讀/寫)所在的記憶體位置。如果幸運,那裡不會有其他程式碼被裝載,我們將得到一個惡性的錯誤資訊。如果不行,另一個核心模組會被裝載到同一個位置,這將意味著會跳入核心中另一個程式的中間,結果將是不可預料的惡劣。
通常你不希望一個函式做什麼事情的時候,會從那個函式返回一個錯誤碼(一個負數)。但這在cleanup_module中是不可能的,因為它是一個void型的函式。一旦cleanup_module被呼叫,這個模組就死掉了。然而有一個計數器記錄著有多少個核心模組在使用這個模組,這個計數器稱為索引計數器(/proc/modules中沒行的最後一個數字)。如果這個數字不是0,刪除就會失敗。模組的索引計數器包含在變數mod_use_count_中。有定義好的處理這個變數的宏(MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT),所以我們一般使用宏而不是直接使用變數mod_use_count_,這樣在以後實現變化的時候會帶來安全性。

ex chardev.c

/* chardev.c
* Copyright (C) 1998-1999 by Ori Pomerantz
*
* Create a character device (read only)
*/

/* The necessary header files */

/* Standard in kernel modules */
#include /* Were doing kernel work */
#include /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include
#endif

/* For character devices */
#include /* The character device
* definitions are here */
#include /* A wrapper which does
* next to nothing at
* at present, but may
* help for compatibility
* with future versions
* of Linux */


/* In 2.2.3 /usr/include/linux/version.h includes
* a macro for this, but 2.0.35 doesnt - so I add
* it here if necessary. */
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif


/* Conditional compilation. LINUX_VERSION_CODE is
* the code (as per KERNEL_VERSION) of this version. */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
#include /* for put_user */
#endif



#define SUCCESS 0


/* Device Declarations **************************** */

/* 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

/* Is the device open right now? Used to prevent
* concurent access into the same device */
static int Device_Open = 0;

/* The message the device will give when asked */
static char Message[BUF_LEN];

/* How far did the process reading the message
* get? Useful if the message is larger than the size
* of the buffer we get to fill in device_read. */
static char *Message_Ptr;


/* This function is called whenever a process
* attempts to open the device file */
static int device_open(struct inode *inode,
struct file *file)
{
static int counter = 0;

#ifdef DEBUG
printk ("device_open(%p,%p) ", inode, file);
#endif

/* This is how you get the minor device number in
* case you have more than one physical device using
* the driver. */
printk("Device: %d.%d ",
inode->i_rdev >> 8, inode->i_rdev & 0xFF);

/* We dont want to talk to two processes at the
* same time */
if (Device_Open)
return -EBUSY;

/* If this was a process, we would have had to
* be more careful here.
*
*In the case of processes, the danger would be
*that one process might have check Device_Open
*and then be replaced by the schedualer by another
*process which runs this function. Then, when
*the first process was back on the CPU, it would assume
*the device is still not open.
* However, Linux guarantees that a process wont
* be replaced while it is running in kernel context.
*
* In the case of SMP, one CPU might increment
*Device_Open while another CPU is here, right after the check.
*However, in version 2.0 of the kernel this is not a problem
*because theres a lock to guarantee only one CPU will
*be kernel module at the same time.
*This is bad in terms of performance, so version 2.2 changed it.
*Unfortunately, I dont have access to an SMP box
*to check how it works with SMP.
*/

Device_Open++;

/* Initialize the message. */
sprintf(Message,
"If I told you once, I told you %d times - %s",
counter++,
"Hello, world ");
/* The only reason were allowed to do this sprintf
* is because the maximum length of the message
* (assuming 32 bit integers - up to 10 digits
* with the minus sign) is less than BUF_LEN, which
* is 80. BE CAREFUL NOT TO OVERFLOW BUFFERS,
* ESPECIALLY IN THE KERNEL!!!
*/

Message_Ptr = Message;

/* Make sure that the module isnt removed while
* the file is open by incrementing the usage count
* (the number of opened references to the module, if
* its not zero rmmod will fail)
*/
MOD_INC_USE_COUNT;

return SUCCESS;
}


/* This function is called when a process closes the
* device file. It doesnt have a return value in
* version 2.0.x because it cant fail (you must ALWAYS
* be able to close a device). In version 2.2.x it is
* allowed to fail - but we wont let it.
*/
#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

/* Were now ready for our next caller */
Device_Open --;

/* Decrement the usage count, otherwise once you
* opened the file youll never get rid of the module.
*/
MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
return 0;
#endif
}


/* This function is called whenever a process which
* have already opened the device file attempts to
* read from it. */


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t device_read(struct file *file,
char *buffer, /* The buffer to fill with data */
size_t length, /* The length of the buffer */
loff_t *offset) /* Our offset in the file */
#else
static int device_read(struct inode *inode,
struct file *file,
char *buffer, /* The buffer to fill with
* the data */
int length) /* The length of the buffer
* (mustnt write beyond that!) */
#endif
{
/* Number of bytes actually written to the buffer */
int bytes_read = 0;

/* If were at the end of the message, return 0
* (which signifies end of file) */
if (*Message_Ptr == 0)
return 0;

/* Actually put the data into the buffer */
while (length && *Message_Ptr) {

/* Because the buffer is in the user data segment,
* not the kernel data segment, assignment wouldnt
* work. Instead, we have to use put_user which
* copies data from the kernel data segment to the
* user data segment. */
put_user(*(Message_Ptr++), buffer++);


length --;
bytes_read ++;
}

#ifdef DEBUG
printk ("Read %d bytes, %d left ",
bytes_read, length);
#endif

/* Read functions are supposed to return the number
* of bytes actually inserted into the buffer */
return bytes_read;
}




/* This function is called when somebody tries to write
* into our device file - unsupported in this example. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t device_write(struct file *file,
const char *buffer, /* The buffer */
size_t length, /* The length of the buffer */
loff_t *offset) /* Our offset in the file */
#else
static int device_write(struct inode *inode,
struct file *file,
const char *buffer,
int length)
#endif
{
return -EINVAL;
}




/* Module Declarations ***************************** */

/* The major device number for the device. This is
* global (well, static, which in this context is global
* within this file) because it has to be accessible
* both for registration and for release. */
static int Major;

/* This structure will hold the functions to be
* called when a process does something to the device
* we created. Since a pointer to this structure is
* kept in the devices table, it cant be local to
* init_module. NULL is for unimplemented functions. */


struct file_operations Fops = {
NULL, /* seek */
device_read,
device_write,
NULL, /* readdir */
NULL, /* select */
NULL, /* ioctl */
NULL, /* mmap */
device_open,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
NULL, /* flush */
#endif
device_release /* a.k.a. close */
};


/* Initialize the module - Register the character device */
int init_module()
{
/* Register the character device (atleast try) */
Major = module_register_chrdev(0,
DEVICE_NAME,
&Fops);

/* Negative values signify an error */
if (Major < 0) {
printk ("%s device failed with %d ",
"Sorry, registering the character",
Major);
return Major;
}

printk ("%s The major device number is %d. ",
"Registeration is a success.",
Major);
printk ("If you want to talk to the device driver, ");
printk ("youll have to create a device file. ");
printk ("We suggest you use: ");
printk ("mknod c %d ", Major);
printk ("You can try different minor numbers %s",
"and see what happens. ");

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 theres an error, report it */
if (ret < 0)
printk("Error in unregister_chrdev: %d ", ret);
}
2.1多核心版本原始檔
系統呼叫是核心出示給程式的主要介面,在不同版本中一般是相同的。可能會增加新的系統,但是舊的系統的行為是不變的。向後相容是必要的——新的核心版本不能打破正常的程式規律。在大多數情況下,裝置檔案是不變的。然而,核心中的內部介面是可以在不同版本間改變的。
Linux核心的版本分為穩定版(n..m)和發展版(n..m)。發展版包含了所有新奇的思想,包括那些在下一版中被認為是錯的,或者被重新實現的。所以,你不能相信在那些版本中這些介面是保持不變的(這就是為什麼我在本書中不厭其煩的支援不同介面。這是很大量的工作但是馬上就會過時)。但是在穩定版中我們就可以認為介面是相同的,即使在修正版中(數字m所指的)。
MPG版本包括了對核心2.0.x和2.2.x的支援。這兩種核心仍有不同之處,所以編譯時要取決於核心版本而決定。方法是使用宏LINUX_VERSION_CODE。在a.b.c版中,這個宏的值是216a+28b+c。如果希望得到具體核心版本號,我們可以使用宏KERNEL_VERSION。在2.0.35版中沒有定義這個宏,在需要時我們可以自己定義。

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

相關文章