設計一個資源管理系統對于一個綜合性的資源通路系統來說是十分必要的,而好的設計一定是不複雜的,甚至說是十分簡單的,原因就是精簡機構,消除備援,或者說精兵簡政在任何時候任何方面總是必要的,我們不希望管理機制本身消耗大量的資源(時間,空間,或者其它),是以在設計之前一定要有好的實體抽象,這些實體抽象不一定要多但一定要完整。我自認為自己的一個設計是比較不錯的,實話說,它真的不錯,如果你否認這一點的話,請看完本文,最終你會明白我為何如此自戀。首先抽象出受控程序,控制元素以及管理組的概念,在詳細分析設計步驟之前,先把這些抽象的概念形象化一下:
受控程序:ssh,sshd,httpd...
控制元素:cpu占用率,記憶體總量,磁盤使用...
管理組:組1,組2,組3
控制粒度:基于程序和控制元素,也就是說管理組(組x)不必控制控制元素中的所有元素,隻需要控制其中一個或者幾個即可,當然也可以是全部。
有了以上假設,那麼幾種對應關系就很顯然了,指出這些對應關系之前首先定義幾種角色:
主體:受控程序
客體:控制元素
管理者:管理組
總體描述:主體在管理者的管理下對客體産生動作。
以上這些都是最最基本的元素了,我認為不能比這更簡單了,是以我認為它是完美的,了解了角色和總的描述,它們的關系如下:
受控程序->控制元素:這是個一對多的關系,一個程序可以接受多種元素的控制
控制元素->受控程序:這是個一對多的關系,一個控制元素可以控制多個程序
受控程序<=>控制元素:是以這是個多對多的關系
控制元素->管理組:這是個一對一的關系,即某個程序的一個受控元素隻能由一個管理組控制
管理組->控制元素:這是個一對多的關系,即一個管理組可以管理一組程序的多個控制元素
管理組<=>受控程序:這是個多對多的關系,即一個管理組可以可以控制多個程序,反過來一個程序的不同控制元素也能被多個管理組控制。
有了以上的關系,我想任何關系資料庫的高手都能設計出表結構來的,甚至都不需要是高手,隻要會一些資料庫的知識應該就不成問題,這個關系結構很顯然,表結構也很簡單可是在不允許查詢資料庫或者沒有資料庫環境的情況下,如何實作這個方案呢?比如在一個嵌入式的小記憶體的裝置上,如何僅僅通過c語言或者别的輕量級語言程式設計實作它呢?要知道資料庫的查詢操作實際上僅僅給外部提供一個接口,而其實作是由查詢引擎完成的,在這個意義上,我們要實作的就是一個小型的查詢引擎了,是以首先我們需要先設計一下資料結構,最基本的就是主體,客體,管理者的設計了:
主體:
struct task {
void *other;
List *targets;
}Task;
客體:
struct target {
List *tasks;
Manager *man;
}Target;
管理者
struct manager {
}Manager;
以上是最初步的設計,已經展現了對應關系,但是無法索引目前的客體,是以有必要将所有的客體組織成數組的形式:
Target ts[] = {cpu,memory,file,...};
主體重新設計為:
Target targets[N];
但是這樣的話,每一個Target的結構體中除了tasks字段之外的資料将會重複,這就浪費了記憶體,是以有必要将task和target進行解除耦合,于是設計一個中間結構,用于拼接這兩個結構:
struct middle {
List *tasks; //所有的使用這個middle的task連結清單
Target targets[N]; //以上tasks這些程序的N控制元素
}Mid;
Mid *mid;
這樣,所有的被同一些控制元素控制的task就可以共享一個target了,進而節省了記憶體。由于task和target是多對多的關系,是以使用了一個middle來解除其耦合,達到了共享一方的效果,同樣的道理,task和manager的關系也是多對多的關系,于是同樣的措施必然帶來同樣的效果。于是重新設計:
struct middle2 {
List *tasks; //所有的被這個管理者管理的task連結清單
List *managers; //一個task屬于的所有的管理者
}Mid2;
Mid2 *mid2;
仔細一想,這樣不妥,原因有二,第一個原因是一個task雖然屬于很多manager,但是它是基于target的,這個資訊沒有展現出來,第二,資訊備援,這樣在修改或者同步起來非常不便,于是必然要重新設計,由于manager和target的聯系已經建立了,然而這個關系是單向的,資料查詢是以也隻能是單向的,我們能通過一個task得到它歸于哪些manager,卻無法反過來從一個manager得到它都控制了哪些task,由于管理者基于一組target來分組,是以所有這些task擁有共同的有效target[N],是以必然需要建立一個反向的關系,首先在Manager中建立一個新的List字段:
從基于target組進行管理的前提并且task和target已經通過middle聯系得知,這個manager需要和middle用類似middile的結構體進行聯系,姑且将該結構體設為middle2,這個middle2對于manager來說,它代表一個middle,也就是一組task以及對應的一組target,這個middle2對于一個middle來說,它代表一個manager,為了表現manager和middle是對對多的關系,middle2的設計和middle的設計幾乎一樣:
List *manager; //對于一個middle來說,該連結清單包含所有的targets數組的manger字段的并集,當然由于一個middle對應N個target,具體哪個target對應哪個manager,需要查閱middle的targets數組來一一确定。
List *mids; //對于一個manager來說,該連結清單包含它所參與管理的所有的middle
Mid *mid; //一個mid2唯一對應一個mid。這是基于targets集合意義的,而不是基于單獨的target的
最終的設計如下:
Target targets[N]; //這裡使用數組而沒有使用List完全是為了代碼的簡潔,可以直接通過enum枚舉名來直接通路
List *tasks;
List *mid2; //由于下面的N個target并不一定屬于一個manager,是以一個middle可能也屬于多個manager,這個關系通過mid2來耦合。
Target targets[N];
List *manager;
List *mids;
Mid *mid;
List *mid2;
這麼一個設計基本就完美了,我們試一下幾個查詢。首先,從一個task查詢一下它的各個控制元素都受哪個管理者的控制,這個很簡單,從task取出mid字段,然後周遊整個targets數組,從每一個元素中取出manager字段即可;接下來根據一個manager查詢一下它都控制了哪些程序,取出manager的mid2字段,周遊其中的每一個middle2元素,對于每一個middle2,取出其mid字段,然後周遊其task字段,取出所有的task即可。着重需要說明的是middle2結構,它具有兩重身份,mids是作為單獨的target起作用的,畢竟一個middle結構體擁有不同的target,這些target不一定有相同的manager的,而mid字段是作為一組targets起作用的,換句話說,它針對的是一個middle而不是middle中單獨的target。
本設計可以使用資料庫,但是卻沒有使用資料庫,因為本設計的精髓在于設計的實作而不是關系模型,可以看出,linux核心在設計資料結構的時候和本文的方式有些類似,實際情況是:本文就是參考linux核心的cgroups做出來的,是以我才敢說它很完美(盡管linux核心并不總是很完美)!看完了本文,再去了解linux核心代碼中關于cgroups的複雜代碼,我相信就清晰多了。在此給出一些對應:
task--task_struct
target--cgroup_subsys_state
manager--cgroup
middle--css_set
middle2--cg_cgroup_link
其中在find_existing_css_set函數中有下面的判斷:
if (root->subsys_bits & (1UL << i))
這說明,雖然你建立了一個新的cgroup,然後這個cgroup中具體哪些subsys起作用,所依據的是你mount cgroup時的-o參數,是以雖然将一個程序放入了一個cgroup,如果這個cgroup在mount的時候沒有memory參數而隻有cpu參數的話,該程序的記憶體使用情況還将受原先cgroup的限制而不受這個新的cgroup的限制。另外需要注意的是,linux核心的cgroup使用了OO的思想,cgroup_subsys_state并不提供任何資訊,如果你想提供一些資訊,比如想提供記憶體使用方面的限制,那麼建立一個結構體,讓cgroup_subsys_state作為第一個元素(或者第n個元素),這樣cgroup_subsys_state就是一個根類對象,讓所有其它的具體類繼承它即可:
struct memctrl {
cgroup_subsys_state cst;
...//其它的具體字段
};
然後用container_of取出具體的結構即可!
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271178