轉自:http://dev.firnow.com/course/6_system/linux/linuxjq/20100313/198611.html
在學習Linux驅動的過程中,遇到一個宏叫做container_of。
該宏定義在include/linux/kernel.h中,首先來貼出它的代碼:
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
* */
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
它的作用顯而易見,那就是根據一個結構體變量中的一個域成員變量的指針來擷取指向整個結構體變量的指針。
比如,有一個結構體變量,其定義如下:
struct demo_struct {
type1 member1;
type2 member2;
type3 member3;
type4 member4;
};
struct demo_struct demo;
同時,在另一個地方,獲得了變量demo中的某一個域成員變量的指針,比如:
type3 *memp = get_member_pointer_from_somewhere();
此時,如果需要擷取指向整個結構體變量的指針,而不僅僅隻是其某一個域成員變量的指針,我們就可以這麼做:
struct demo_struct *demop = container_of(memp, struct demo_struct, member3);
這樣,我們就通過一個結構體變量的一個域成員變量的指針獲得了整個結構體變量的指針。
下面說一說我對于這個container_of的實作的了解:
首先,我們将container_of(memp, struct demo_struct, member3)根據宏的定義進行展開如下:
struct demo_struct *demop = ({ \
const typeof( ((struct demo_struct *)0)->member3 ) *__mptr = (memp); \
(struct demo_struct *)( (char *)__mptr - offsetof(struct demo_struct, member3) );})
其中,typeof是GNU C對标準C的擴充,它的作用是根據變量擷取變量的類型。
是以,上述代碼中的第2行的作用是首先使用typeof擷取結構體域變量member3的類型為type3,
然後定義了一個type3指針類型的臨時變量__mptr,并将實際結構體變量中的域變量的指針memp的值賦給臨時變量__mptr。
(char *)__mptr轉換為位元組型指針。
( (char *)__mptr - offsetof(type,member) )用來求出結構體起始位址(為char *型指針),
然後(type *)( (char *)__mptr - offsetof(type,member) )在(type *)作用下進行将位元組型的結構體起始指針轉換為type *型的結構體起始指針。
假設結構體變量demo在實際記憶體中的位置如下圖所示:
demo
+-------------+ 0xA000
| member1 |
+-------------+ 0xA004
| member2 |
+-------------+ 0xA010
| member3 |
+-------------+ 0xA018
| member4 |
+-------------+
則,在執行了上述代碼的第2行之後__mptr的值即為0xA010。
再看上述代碼的第3行,其中需要說明的是offsetof,它定義在include/linux/stddef.h中,其定義如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
先分析一下這個宏的運作機理:
一共4步
- ( (TYPE *)0 )将零轉型為TYPE類型指針;
- ((TYPE *)0)->MEMBER通路結構中的資料成員;
- &( ( (TYPE *)0 )->MEMBER )取出資料成員的位址;
- (size_t)(&(((TYPE*)0)->MEMBER))結果轉換類型。
巧妙之處在于将0轉換成(TYPE*),結構以記憶體空間首位址0作為起始位址,則成員位址自然為偏移位址;
offsetof就是取結構體中的域成員相對于位址0的偏移位址,也就是域成員變量相對于結構體變量首位址的偏移。
是以,offsetof(struct demo_struct, member3)調用傳回的值就是member3相對于demo變量的偏移。
結合上述給出的變量位址分布圖可知,offsetof(struct demo_struct, member3)将傳回0x10。
于是,由上述分析可知,此時,__mptr==0xA010,offsetof(struct demo_struct, member3)==0x10。
是以,(char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) == 0xA010 - 0x10 == 0xA000,也就是結構體變量demo的首位址(如上圖所示)。
這就是從結構體某成員變量指針來求出該結構體的首指針。指針類型從結構體某成員變量類型轉換為該結構體類型。
由此,container_of實作了根據一個結構體變量中的一個域成員變量的指針來擷取指向整個結構體變量的指針的功能。
以上内容載自網絡,這篇文章分析的很透徹,順便說一下,宋寶華的《linux裝置驅動開發詳解》P132 最後一行當中對該宏的參數解釋是錯誤的!當然了,暇不掩瑜!
以下是我自己的一些了解:
首先,我定義了一個字元裝置結構體
struct globalmem_dev
{
struct cdev my_cdev; //字元裝置之基礎結構體
unsigned char mem[GLOBALMEM_SIZE];
struct semaphore sem;
};
接下來我執行個體化了一個該裝置的指針對象
struct globalmem_dev *pdev;
後來在open函數中我是這麼來用的
int globalmem_open(struct inode *inode, struct file *filp) //關于filp的産生和消亡參見《驅動詳解》P92
{
struct globalmem_dev *pdev;
printk("\nFunction globalmem_open Invoked\n");
pdev = container_of(inode->i_cdev, struct globalmem_dev, my_cdev);
filp->private_data = pdev;
if(down_trylock(&pdev->sem))//獲得信号量
return -EBUSY;
return 0;
}
對以上用法的說明:
參數3是參數2這個結構體的一個成員的名字!而不是類型名!
參數1是一個指針,它指向參數3這個成員
inode中的i_cdev字段是一個指針,當我們成功insmod了一個裝置驅動的時候,我們會通過mknod建立一個裝置檔案節點并和具體裝置(驅動)相關聯,這個裝置檔案節點所對應的就是struct inode結構體的一個執行個體,這個結構體有一個字段i_cdev,是個struct cdev類型的指針,它會指向裝置結構體的my_cdev字段。
至此你已經有了一個指向某個globalmem_dev的my_cdev字段的一個指針(在調用open前pdev的記憶體配置設定假定已經完成)由此container_of可以幫你計算出指向該裝置結構體的指針。
當一個裝置驅動對應多個裝置(子裝置)時,你就知道container_of發揮的作用了!
當你針對每一個裝置調用open時,因為每個裝置對應的裝置檔案節點不一樣,那麼根據該節點的i_cdev字段所計算的裝置結構體指針也不一樣,你就可以找到特定節點所對應的裝置結構體!而不至于對不同的子裝置編寫大同小異的各自的裝置驅動。