Linux內(nèi)核宏container_of的深度剖析
1、前面說(shuō)的
我在好幾年前讀linux 驅(qū)動(dòng)代碼的時(shí)候看到這個(gè)宏,百度了好久,知道怎么用了,但是對(duì)實(shí)現(xiàn)過(guò)程和原理還是一知半解。
container_of宏 在linux內(nèi)核代碼里面使用次數(shù)非常非常多,對(duì)于喜歡linux編程的同學(xué)來(lái)說(shuō),了解其實(shí)現(xiàn)方法,對(duì)以后看內(nèi)核代碼,寫內(nèi)核驅(qū)動(dòng)的幫助都非常大,當(dāng)然,我不是說(shuō)了解這個(gè)就可以為所欲為了,內(nèi)核博大精深,先宏觀再微觀去學(xué)習(xí),不積跬步何以致千里,不要想著一口就能吃成一個(gè)胖子,我這篇文章主要剖析一下這個(gè)函數(shù)的實(shí)現(xiàn)原理,希望對(duì)大家學(xué)習(xí)過(guò)程中有所幫助。
android7.1/kernel/drivers/input kernel/drivers/input$ grep -rn container_of ./|wc -l 710 android7.1/kernel/drivers/input$
使用grep -rn container_of ./|wc -l統(tǒng)計(jì)了下kernel/drivers/input/目錄下的container_of出現(xiàn)的次數(shù),一共有710次使用。
2、container_of的作用
container_of的作用的通過(guò)結(jié)構(gòu)體成員變量地址獲取這個(gè)結(jié)構(gòu)體的地址,假設(shè)你的名字叫李光明,你還有一個(gè)弟弟叫做XXX,警察叔叔發(fā)現(xiàn)你弟弟XXX干了一件壞事,但是警察叔叔不知道你弟弟的名字,抓你來(lái)審問(wèn),你嘴巴很硬就是不說(shuō),警察叔叔就拿到你的名字,查到了你家的戶口本,這下你弟弟就被查出來(lái)了,原來(lái)你弟弟XXX的名字叫做李小明。這種破案手法叫做順藤摸瓜。
內(nèi)核函數(shù)調(diào)用常常給函數(shù)傳入的是結(jié)構(gòu)體成員地址,然后在函數(shù)里面又想使用這個(gè)結(jié)構(gòu)體里面的其他成員變量,所以就引發(fā)了這樣的問(wèn)題,我認(rèn)為這個(gè)也是用C實(shí)現(xiàn)面向?qū)ο缶幊痰囊环N方法。
比如這段代碼
static void sensor_suspend(struct early_suspend *h) { struct sensor_private_data *sensor = container_of(h, struct sensor_private_data, early_suspend); if (sensor->ops->suspend) sensor->ops->suspend(sensor->client); }
early_suspend是sensor_private_data 里面的一個(gè)成員,通過(guò)這個(gè)成員的地址獲取sensor_private_data結(jié)構(gòu)體變量的地址,從而調(diào)用里面的成員變量client。這個(gè)方法非常優(yōu)雅。在這里我用到了一個(gè)比較叼的詞,叫“優(yōu)雅”。
這里簡(jiǎn)單說(shuō)下,傳進(jìn)來(lái)的h一定在其他地方定義并且操作系統(tǒng)分配了內(nèi)存空間,h分配了空間,說(shuō)明他也有內(nèi)存了,要不然你順藤摸瓜摸到一個(gè)NULL。
3、如何使用container_of
container_of需要傳入三個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)指針,第二個(gè)參數(shù)是結(jié)構(gòu)體類型,第三個(gè)是對(duì)應(yīng)第二個(gè)參數(shù)里面的結(jié)構(gòu)體里面的成員。
container_of(ptr, type, member)
- ptr:表示結(jié)構(gòu)體中member的地址 h
- type:表示結(jié)構(gòu)體類型 struct sensor_private_data
- member:表示結(jié)構(gòu)體中的成員 early_suspend type里面一定要有這個(gè)成員,不能瞎搞啊
- 返回結(jié)構(gòu)體的首地址
4、container_of 用到的知識(shí)點(diǎn) 剖析
4.1、({})的作用
({})、第一個(gè)先說(shuō)這個(gè)表達(dá)式,很多人可能懂,可能在很多地方見到這個(gè)表達(dá)式,但是自己卻沒(méi)有注意,這個(gè)表達(dá)式返回最后一個(gè)表達(dá)式的值。比如x=({a;b;c;d;}),最終x的值應(yīng)該是d。
代碼例子:
#include<stdio.h> void main(void) { int a=({1;2;4;})+10; printf("%d\n",a);//a=14 }
4.2、typeof獲取變量的類型
這個(gè)我們很少看到,這個(gè)關(guān)鍵字是C語(yǔ)言關(guān)鍵字的拓展,返回變量的類型,具體可以看GCC里面的介紹
https://gcc.gnu.org/onlinedocs/gcc/Typeof.html
++Another way to refer to the type of an expression is with typeof. The syntax of using of this keyword looks like sizeof, but the construct acts semantically like a type name defined with typedef.++
代碼例子:
void main(void) { int a = 6; typeof(a) b =9; printf("%d %d\n",a,b); }
4.3、(struct st*)0的作用
尺子大家應(yīng)該都用過(guò)吧,比如我想用尺子量一本書本的長(zhǎng)度,我們第一時(shí)間就需要找到尺子的0刻度的位置,然后用這個(gè)0刻度的位置去對(duì)準(zhǔn)書本的邊,然后再貼合對(duì)齊,在書本的另一邊查看尺子刻度就可以知道書本的長(zhǎng)度了。
現(xiàn)在我們需要量一個(gè)結(jié)構(gòu)體的長(zhǎng)度,我們也可以用尺子來(lái)量,我們只要找到這個(gè)0刻度的位置就可以了。同理,即使我們不知道0刻度位置,我們首尾刻度相減一樣可以計(jì)算出結(jié)構(gòu)體的長(zhǎng)度。
但是在C語(yǔ)言里什么事尺子呢?你想到的可能是sizeof,不幸的是,這個(gè)并不能滿足我們的需要,所以才有了(struct st *),這個(gè)當(dāng)作尺子真的再好不過(guò)了。
struct st{ int a; int b; }*p_st,n_st; void main(void) { printf("%p\n",&((struct st*)0)->b); }
上面的代碼
(struct st*)0
這個(gè)的意思就是把這個(gè)結(jié)構(gòu)體放到0刻度上面開始量了,然后量到哪里呢?
&((struct st*)0)->b)
這個(gè)就體現(xiàn)出來(lái)了,量到b的位置。所以上面的輸出應(yīng)該是4。
看完上面的解釋,應(yīng)該知道下面這兩個(gè)代碼的功能是一樣的。
typeof ((struct st*)0)->b) c; // 取b的類型來(lái)聲明c int c;
其實(shí)不只是對(duì)于0,用其他數(shù)字一樣是有效的,比如下面的代碼,編譯器關(guān)心的是類型,而不在乎這個(gè)數(shù)字。
printf("%p\n",&((struct st*)4)->b -4 );
這文章寫了有幾天了,但是一直不想直接發(fā)出去,因?yàn)槲矣X得這個(gè)核心點(diǎn)總是沒(méi)有找到一個(gè)特別好的論證方法,看完上面后,大概對(duì)這種測(cè)量應(yīng)該有點(diǎn)感覺了吧,如果現(xiàn)在需要你把一個(gè)數(shù)組的首地址設(shè)置為0,要怎么做呢?
先思考一下,假設(shè)這里延遲了幾分鐘。
代碼如下:
struct A { short array[100]; }; int main(int argc, char *argv[]) { int i = 10; A* a = (A*)0; printf("%p %d %d\n",a,sizeof(short), &a->array[20]); getchar(); return 1; } //輸出 00000000 2 40
有什么辦法不使用==struct A *== 直接把數(shù)組的地址放到0位置呢?目前我還沒(méi)有找到其他更好的辦法,如果有好的建議的,請(qǐng)留言給我。
4.4、offsetof(TYPE, MEMBER)
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
size_t 這個(gè)有不懂的可以百度下,就是unsigned 的整數(shù),在32位和64位下長(zhǎng)度不同,所以這個(gè)offsetof就是獲取結(jié)構(gòu)體的偏移長(zhǎng)度。
4.5、const int* p的作用
上面的宏定義里面還有一個(gè)小知識(shí)點(diǎn)
const typeof( ((type *)0)->member ) *__mptr
上面的代碼可以簡(jiǎn)寫成
const int * __mptr
這個(gè)說(shuō)明什么問(wèn)題呢?這個(gè)說(shuō)明__mptr指向的整型數(shù)據(jù)是一個(gè)const(常數(shù))。
這就涉及到兩外兩個(gè)知識(shí)
int * const __mptr;//表示__mptr的值不能改變 //和 const int * const __mptr; //表示__mptr不能改變而且指向的內(nèi)容也不能改變
5、 container_of 剖析
看完上面的幾個(gè)知識(shí)點(diǎn),再來(lái)看container_of這個(gè)宏就顯得非常清晰了。我把解析部分寫在下面的代碼注釋里面。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER) #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (const typeof( ((type *)0)->member ) *)(ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) //-----分割線 struct st{ int a; int b; }*pt; //用這個(gè)來(lái)舉例 container_of(&pt->a,struct st,a) const typeof( ((struct st *)0)->a ) *__mptr = (const typeof( ((struct st *)0)->a ) *)(&pt->a); const int *__mptr = (int *)(&pt->a);//第一句解析完,實(shí)際上就是獲取a的地址。 (type *)( (char *)__mptr - offsetof(type,member) ); //這個(gè)變成 (struct st *)( (char *)__mptr - ((unsigned int) &((struct st*)0)->a)); //這句的意思,把a(bǔ)的地址減去a對(duì)結(jié)構(gòu)體的偏移地址長(zhǎng)度,那就是結(jié)構(gòu)體的地址位置了。
6、實(shí)例代碼
經(jīng)過(guò)上面的解釋,至少對(duì)這個(gè)宏有感覺了吧,寫個(gè)代碼來(lái)測(cè)試一下,讓自己與代碼融合為一體,這樣才能做到人碼合一的境界。
代碼如下:
#include<stdio.h> #include<stddef.h> #include<stdlib.h> #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER) /*ptr 成員指針 * type 結(jié)構(gòu)體 比如struct Stu * member 成員變量,跟指針對(duì)應(yīng) * */ #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (const typeof( ((type *)0)->member ) *)(ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) typedef struct Stu{ int age; char name[10]; int id; unsigned long phone_num; }*p_stu,str_stu; void print_all(void *p_str) { p_stu m1p_stu = NULL; m1p_stu = container_of(p_str,struct Stu,age); printf("age:%d\n",m1p_stu->age); printf("name:%s\n",m1p_stu->name); printf("id:%d\n",m1p_stu->id); printf("phone_num:%d\n",m1p_stu->phone_num); } void main(void) { p_stu m_stu = (p_stu)malloc(sizeof(str_stu)); m_stu->age = 25; m_stu->id = 1; m_stu->name[0]='w'; m_stu->name[1]='e'; m_stu->name[2]='i'; m_stu->name[3]='q'; m_stu->name[4]='i'; m_stu->name[5]='f'; m_stu->name[6]='a'; m_stu->name[7]='\0'; m_stu->phone_num=13267; /*傳結(jié)構(gòu)體成員指針進(jìn)去*/ print_all(&m_stu->age); printf("main end\n"); if(m_stu!=NULL) free(m_stu); }
7、程序輸出
age:25
name:weiqifa
id:1
phone_num:13267
main end
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接
相關(guān)文章
實(shí)戰(zhàn) LAMP 服務(wù)器配置 完整篇
LAMP 服務(wù)器配置 按部就班篇,大家可以學(xué)習(xí)試試。2009-06-06Apache Shiro 使用手冊(cè)(四) Realm 實(shí)現(xiàn)
在認(rèn)證、授權(quán)內(nèi)部實(shí)現(xiàn)機(jī)制中都有提到,最終處理都將交給Real進(jìn)行處理。因?yàn)樵赟hiro中,最終是通過(guò)Realm來(lái)獲取應(yīng)用程序中的用戶、角色及權(quán)限信息的2014-06-06CentOS Linux系統(tǒng)搭建Android開發(fā)環(huán)境詳細(xì)介紹
這篇文章主要介紹了CentOS Linux系統(tǒng)搭建Android開發(fā)環(huán)境詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-11-11在Ubuntu/Linux環(huán)境下使用MySQL開放/修改3306端口和開放訪問(wèn)權(quán)限
這篇文章主要介紹了在Ubuntu/Linux環(huán)境下使用MySQL開放/修改3306端口和開放訪問(wèn)權(quán)限,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07教大家?guī)追N在Linux系統(tǒng)中查看時(shí)區(qū)的方法
Linux 包含多種可用的時(shí)間管理工具,比如 date 或 timedatectlcommands,你可以用它們來(lái)獲取當(dāng)前系統(tǒng)時(shí)區(qū),也可以將系統(tǒng)時(shí)間與 NTP 服務(wù)器同步,來(lái)自動(dòng)地、更精確地進(jìn)行時(shí)間管理。下面通過(guò)這篇文章來(lái)一起看看在Linux系統(tǒng)中查看時(shí)區(qū)的幾種不同方法。2017-01-01詳解Xampp和wordpress在Centos7上的搭建與使用
通過(guò)實(shí)例給大家詳細(xì)分析了Xampp和wordpress在Centos7上的搭建與使用,對(duì)此有需要的朋友可以學(xué)習(xí)下。2018-02-02