Trường Đại Học Bách Khoa Hà Nội
Viện Điện Tử Viễn Thông
====o0o====
BÁO CÁO BÀI TẬP DÀI MÔN HỆ
ĐIỀU HÀNH
Bài tập số 2:
Chương trình sửa mã nguồn hệ điều hành Linux báo cáo dung lượng
Ram và thu hồi bộ nhớ theo cơ chế slab cache
Giáo viên hướng dẫn : PHẠM VĂN TIẾN
Sinh viên thực hiện : Lương Kim Doanh
Ngô Quang Thìn
Trần Hoàng Điệp
Nguyễn Trung Thành
Lớp : KSTN-ĐTVT-K52
HÀ NỘI 10/2011
1
Mục Lục:
!!"!
#$%%&!
'%(!
)"*#)+!
#!%,%#)+-
#.%./012#)+-
3")"*#)+-
!4!!"-
5!06%7899:!!";
<"=>;
?1@%"!@:;
A!9
BC:9D!9
EA%!"94:1!5
F#&1!91GHIJ7KL1-
MN-
#06-
O>"P:-
4JQ:
O4<R*3<F<*5
4JQ>Q
F=1J7K>6S'
T=U
<1!9V
2
I.Giới thiệu
1.Giới thiệu chung
Mục đích của việc quản lí bộ nhớ là cung cấp một phương thức trong đó bộ nhớ có thể
được chia sẻ động giữa những người sử dụng, những tiến trình khác nhau với những mục đích
khác nhau. Phương thức quản lí bộ nhớ sẽ thực hiện hai nhiệm vụ:
- Tối thiểu lượng thời gian yêu cầu quản lí bộ nhớ
- Lượng bộ nhớ sử dụng sẵn có là lớn nhất (tối thiểu lượng nhớ tiêu tốn cho quản lí)
Việc quản lí bộ nhớ lý tưởng là thực hiện tốt cả hai nhiệm vụ trên, nhưng thực tế không
dễ dàng thực hiện chúng cùng một lúc và chùng thường có xu hướng mâu thuẫn với nhau.
Chúng ta có thể phát triển một thuật toán mà sử dụng ít bộ nhớ nhưng sẽ phải mất nhiều
thời gian để quản lí bộ nhớ sẵn có. Chùng ta cũng có thể phát triển một thuật toán mà
quản lí bộ nhớ một cách hiệu quả về thời gian nhưng sẽ tốn nhiều tài nguyên hệ thống
hơn. Kết cục một ứng dụng cụ thể chỉ ưu tiên chọn một trong hai xu hướng phú hợp nhất.
Việc quản lí bộ nhớ trước đây dựa vào chiến lược cấp phát dựa vào heap. Trong
phương thức này một khối bộ nhớ lớn (người ta gọi la heap) được sử dụng để cấp bộ nhớ
cho mục đích của người sử dụng. Khi người sử dụng cần một khối bộ nhớ, họ yêu cầu
heap manager tìm kiếm lượng bộ nhớ thoả mãn và trả về khối tìm được. Một vài thuật
toán được sử dụng để tìm kiếm là first-fit (khối đấu tiên tìm được trong heap thỏa mãn
yêu cầu). Khi người sử dụng hoàn tất công việc, khối bộ nhớ đươc trả lại cho heap.
Vấn đề chủ yếu phát sinh với chiến lược cấp phát dựa vào heap này là sự phân
mảnh (fragmentation). Khi khối bộ nhớ được cấp phát, chúng sau đó được trả lại theo thứ
tự khác nhau vào những thời điểm khác nhau. Điều này dẫn đến việc để lại những chỗ
trống trong heap, yêu cầu nhiếu thời gian hơn để quản lí bộ nhớ rỗi. Thuật toán này
hường đến việc quản lí tài nguyên bộ nhớ một cách hiệu quả nhưng yêu cầu thời gian để
quản lí heap.
Một phương pháp khác là cấp phát bộ nhớ theo kiểu bè bạn (buddy memory
allocation), là kĩ thuất cấp phát nhanh hơn mà chia bộ nhó thành các phân vùng
(partition) mũ cơ số 2 và thử cấp phát bộ nhớ yêu cấu sử dụng phương pháp best-fit. Khi
bộ nhớ cấp phát cho ngưới sử dụng được giải phóng, khối buddy được kiểm tra để xem
3
liệu bất kì hàng xóm nào của nó đang rỗi hay không. Nếu có, những khối nhớ này được
hợp lại để giảm những lãng phí bộ nhớ do phương pháp best-fit.
2. Giới thiệu slab cache
Bộ cấp phát slab được sử dụng trong Linux dựa vào thuật toán được giới thiệu bởi
Jeff Bonwick cho hệ điều hành SunOS. Giải pháp cấp phát của Jeff Bonwick xoay quanh
việc đệm đối tượng (object caching). Bên trong kernel, một lượng đáng kể bộ nhớ được
cấp phát cho một tập hữu hạn các đối tượng như miêu tả tập tin ( file descriptor) và các
cấu trúc thông dụng khác. Jeff nhận thấy rằng thời gian cần thiết để khởi tạo một object
thông thường trong kernel vượt quá thời gian cần thiết để cấp phát và giải phóng nó. Jeff
đưa ra kết luận rằng thay vì giải phóng bộ nhớ trả lại global pool, ta nên để bộ nhớ duy trì
trạng thái khởi tạo cho mục đích sử dụng nó. Ví dụ, nếu bộ nhớ được cấp phát cho mutex.
Những lần cấp phát sau của bộ nhớ không càn thực hiện khởi tạo vì nó đã ở trạng thái
mong muốn từ lần giải cấp phát trước (deallocation) và gọi đến hàm hủy (deconstructor).
Bộ cấp phát slab sử dụng ý tưởng này và nhiều ý tưởng khác để xây dựng một bộ
cấp phát hiệu quả về không gian và thời gian.
Slab allocator chứa một số cache mà liên kết với nhau bằng danh sách liên kết vòng đôi
(doubly linked circular list) gọi là cache main. Một cahe, trong ngữ cảnh slab allocator, là một
4
quản lí cho một object kiểu nhất định, nhu mm_truct, fs_cache và được quản lí bởi cấu trúc
kmem_cahe.
Mỗi cache giữ một khối các trang liên tiếp trong bộ nhớ được gọi là các slab, các
slab được chia thành các đoạn nhỏ chứa cấu trúc dữ liệu và object mà cache quản lí. Mỗi
slab allocator có 3 nguyên lí:
- Cấp phát những khối bộ nhớ nhỏ để giúp loại bỏ phân mảnh nội gây ra bởi hệ
thống buddy
- Đệm những đối tượng thường sử dụng sao cho hệ thống phải tốn thời gian khởi
tạo, cấp phát, hủy đối tượng
- Sắp xếp những object trong cache L1 và L2 để sử dụng hardware cache tốt hơn.
II: Nội dung
1.Cache:
Mỗi cache tồn tại cho 1 kiểu object mà được đệm. Danh sách đầy đủ những cache hiện có
lúc hệ thống đang chạy có thể được liệt kê sử dụng lệnh: cat/proc/slabinfo
5
Mỗi cột tương ứng với 1 trường của cấu trúc kmem_cache:
name: tên cache mà người sử dụng có thể đọc được .VD:TCPv6
active_objs: số lượng object đang sử dụng
num_objs: tổng số lượng object có thể sử dụng được bao gồm cả những object chưa được sử
dụng
objsize: kích thước của object trong cache
active_slabs : số lượng slab đang được sử dụng của cache
num_clabs: số lượng slab cấp phát cho cache.
Nếu SMP được enable thì thêm 2 trường nữa được thêm vào:
- limit: Số lượng free object mà pool (object pools) có thể có trước khi một nửa được trả lại cho
global pool
- batchcount: là số lượng object được cấp phát cho CPU khi không có object free.
6
Để tăng tốc cấp phát và giải phóng object và slab , chúng được chia a thành ba danh
sách : slabs_full, slabs_partial và slab_free. Tất cả các object trong slabs_full đã được sử dụng,
slabs_partial có object, vì vậy được dung chủ yếu cho việc cấp phát đối tượng, slabs_free không
có đối tượng nào được cấp phát, vì vậy được dung chủ yếu cho việc hủy slab.
1.1.Cache Descriptor:
Tất cả thông tin về 1 cache được lưu trong cấu trúc kmem_cache được khai báo trong
Include/linux/slub_def.h (nếu kernel sử dụng SLUB thì cấu trúc này nằm trong file
include/linux/slub_def.h)
1.2.Những cờ static của cache:
SLUB_HWCACHE_ALIGN: đặt object vào L1 CPU cache
SLUB_CACHE_DMA: cấp phát slab từ vùng nhớ ZONE_DMA
Nếu tùy chọn CÒNIG_SLAB_DEBUG được set sẽ có thêm 1 số tùy chọn:
SLAB_DEBUG_FREE: khi thi hành việc kiểm tra object free.
SLAB_RED_ZONE: đánh dấu object đầu và object cuối và bẫy tràn
SLAB_POISON : “nhiễm độc” object làm cho nó không được cấp phát hay khởi tạo
Để ngăn việc sử dụng sai các cờ, một CREATE_MASK được tạo ra trong mm/slab.c
chứa tất cả các cở được cho phép. Khi một cache được tạo ra, các cờ yêu cầu được so sánh với
CREATE_MASK và được coi như là một lỗi nếu sử dụng sai cờ
1.3. Cache coloring:
Để sử dụng hardware cache tốt hơn, slab allocator sẽ dịch (offset) object trong các slab
khác nhau các khoảng khác nhau phụ thuộc vào khoảng trống còn lại trong slab. Độ dịch được
tính theo đơn vị BYTES_PER_WORD nếu SLAB_HWCACHE_ALIGN không được set, được
tính theo L1_CACHE_BYTES nếu SLAB_HWCACHE_ALIGN được set
Sau quá trình tạo cache, object trong slab được cấp phát, phần dư được sử dụng để dịch
các object (cache coloring). Hai số được khai báo trong cấu trúc kmem_cache liên quan đến việc
dịch object:
Colour:số lần dịch có thể đối với một bộ dịch
Colour_off: độ dịch
Ví dụ, để thuận tiện, giả sử s_mem ( địa chỉ object đầu tiên) trên slab là 0 và có 100 byte
dư trên lab và độ dịch là 32 byte trên L1 cache trên một Pentium II. Trong kịch bản này,
7
object của slab đầu tiên đựoc tạo ra bắt đầu từ 0. Object của slab thứ 2 sẽ bắt đầu ở byte 32,
object của slab thứ 3 sẽ bắt đầu ở byte 64, object của slab thứ 4sẽ bắt đầu ở byte 96, và object
của slab thứ 5sẽ bắt đầu ở byte 0. Với cách dịch như vậy, các object từ mỗi slab sẽ không bị
cache hit trên cùng 1 line. Giá trị của colour =3, colour_off= 32.
1.4.Tạo cache:
Hàm kem_cache_create () có nhiệm vụ tạo cache mới và thêm nó vào cache chain.
Công việc tạo cache được thực hiện như sau:
. Thi hành việc kiểm tra cơ bản cho việc sử dụng sai
. Thực hiện kiểm tra debug nếu CONFIG_SLAB_DEBUG được set.
. Cấp phát một kmem_cache từ cache_cache slab cache
. Chỉnh kích cỡ object đến kích cỡ word
.Tính toán xem có bao nhiêu object sẽ điền đủ một slab
. Chỉnh kích cỡ đối tượng theo L1
. Tính toán color
.Khởi tạo những trường hợp còn lại của cache descriptor
. Thêm cache mới vào cache chain
1.5. Cache reaping :
8
Khi một slab được giải phóng, nó được đặt vào danh sách slabs_free cho sự sử dụng sau
này. Cache không tự động shrink, vì vậy khi swap chú ý rằng bộ nhớ khan hiếm, nó gọi
kmem_cache_reap() để giải phóng không gian nhớ. Hàm này chịu trách nhiệm tìm kiếm một
cache mà sẽ được yêu cầu shrink bộ nhớ nó sử dụng. Cache reaping không tính đến việc node
hay vùng nào đang yêu cầu cấp phát (pressure). Điều này có nghĩa là, với một cấu trúc NUMA
hay HIGH MEMORY, có khả năng kenel dành rất nhiều thời gian giải phóng bộ nhớ từ vùng
không gian yêu cầu cấp phát, nhưng vấn đề này không xảy ra với những kiến trúc như x86, kiến
trúc mà chỉ có một bank memory.
1.6. Cache shrinking :
Khi một cache được chọn để shrink chính nó, những bước sau được thực hiện :
.delete hết tất cả các object trong mỗi CPU cache
. delete tất cả các slab từ slabs_free nếu cờ grow không được set
9
Kmem_cache_shrink() xóa tất cả các slab từ slabs_free và trả lại số page được giải phóng , hàm
cơ sở này được export cho những người sử dụng slab allocator.
10
Hàm thứ hai,_kmem_cache_shrink() giải phóng tất cả các slab từ slabs_free và sau đó
kiểm tra lại xem slabs_partial và slabs_full có rỗng khống. Hàm này chỉ được sử dụng bên trong
module và rất quan trọng khi hủy cache, nó không quan tâm số page được giải phóng chỉ miễn là
cache rỗng.
1.7. Cache Destroying:
Khi 1 mode unload, nó có trách nhiệm phải xóa bỏ bất kì cache nào với hàm
kmem_cache_destroy(). Mã nhân kennel không chủ động destroy cache của nó bởi vì sự tồn tại
của chúng là cần thiết cho hệ thống. Những bước được thực hiện để xóa bỏ cache:
. Delete cache từ cache chain
. Shrink cache để delete tất cả các slab
. giải phogns bất kì cache của mỗi CPU (kfree())
. Delete cache descriptor từ cache_cache.
1.8: Kích cỡ cache:
Linux duy trì hai tập hợp cache dùng cho việc cấp phát lượng bộ nhớ nhỏ mà bình thường
cấp phát page không phù hợp.Một tập hợp được sử dụng cho DMA, và tập hợp còn lại phù hợp
với sử dụng thông thường. Tên mà người sử dụng có thể đọc được là cache cỡ -N và cache cỡ
-N(DMA). Thông tin về kích cỡ cache được lưu trong cấu trúc cache_sizes
struc cache_sizes {
size_t cs_size;
struct kmem_cache * cs_cachep;
#ifdef COFIG_ZONE_DMA
Struct kmem_cache * cs_cachep;
##emdif
};
Cs_size: kích cỡ của khối bộ nhớ
Cs_cachep: cache của các khối dùng cho việc sử dụng bộ nhớ thông thường
11
Cs dmacachep: cache của các khối dùng cho việc sử dụng DMA
Bời vì số lượng các cache trong hệ thống là giới hạn, một dãy kích cỡ cache tĩnh được
khởi tạo tại thời điểm biên dịch, bắt đầu với 32 bytes trên máy dùng page có kích cỡ 4KB và
64KB với kích cỡ trang lớn hơn:
Static cache_sizes_t_cache_sizes[] ={
If PAGE_SIZE == 4096
{32,NULL,NULL},
# endif
{64, NULL, NULL},
{128, NULL, NULL},
{256, NULL, NULL},
{512, NULL, NULL},
{1024, NULL, NULL},
{2048, NULL, NULL},
{4096, NULL, NULL},
{8192, NULL, NULL},
{16384, NULL, NULL},
{32768, NULL, NULL},
{65536, NULL, NULL},
{131072, NULL, NULL},
{0, NULL, NULL},
Hiển nhiên, dãy kích cỡ tĩnh này chứa các bộ đệm từ 2
5
đến 2
17
, dãy này được khởi tạo
vào thời điểm bắt đầu chạy hệ thống
2. Slab:
Cấu trúc quản lý slab:
Struct slab {
12
truct list_head list;
unsigned long colouroff;
void *s_mem; /* bao gồm colour offset*/
unsigned int inuse; /* số lượng object đang được sử dụng trong slab */
kmem_bufctl_t free
unsigned short nodeid;}
list : đây là danh sách liên kết của slab (slabs_free, slabs_partial, slabs_full)
colouroff: độ dịch từ địa chỉ cơ sở của object đầu tiên trong slab
free: đây là 1 dãy bufctl được sử dụng để lưu vị trí của free object
Cấu trúc quản lý slab được giữ trong ( khi cờ CFLGS_OFF_SLAB được clear) hoặc nằm
ngòai slab
13
2.1. Lưu trữ slab Descriptor:
Nếu kích cỡ object lớn hơn 1 ngưỡng ( 512 byte đối với x86), CFLGS_OFF_SLAB được set và
slab descriptor được giữ ngòai slab ở 1 trong những kích cỡ cache. Kích cỡ cache là đủ lớn để
chứa cấu trúc slab, và dãy kmem_bufctl_t. Cách khác là lưu trữ cấu trúc slab và dãy
kmem_bufclt_t trong slab. Dãy kmem_bufctl_t giữ chỉ số của những object chưa được cấp phát,
sẵn sàng được sử dụng khi cần thiết.
2.2 Tạo Slab :
Khi cache được tạo ra, nó là 1 cache rỗng với những danh sách slabs_free, slabs_partial,
slabs_full rỗng. Slab mới được tạo ra và được cấp phát cho cache bằng cách gọi hàm
cache_grow(). Cách này thường được gọi là ’’ cache growing’’ và xảy ra khi không còn object
nào trong slabs_partial và không còn slab nào trong slabs_free.
14
Những tác vụ được thực hiện để tạo slab như sau :
.Thực thi những kiểm tra cơ sở để đảm bảo không có sử dụng sai
. Tính toán color offset cho object trong slab
. Cấp phát bộ nhớ cho slab và slab descriptor
. Liên kết những page được sử dụng cho slab đến slab và cache descriptor
.Khởi tạo object trong slab
.Thêm slab đến cache
2.3 Tracking free object:
15
Slab allocator có 1 phương pháp đơn giản và nhanh chóng xác định chỗ của object rỗi.
Object thuộc về slab và cache được xác định bằng cấu truc page và dãy kmem_bufctl_t, một dãy
số nguyên các chỉ số của object. Số phần tử này bằng số object trong slab.
typedef unsigned int kmem_bufctl_t;
Bởi vì dãy này đứng sau slab descriptor và không có con trỏ trực tiếp đến phần tử đầu tiên một
cách trực tiếp, một macro trợ giúp được khai báo
static inline kmem_bufctl_t *slab_bufctl(struct slab *slabp)
{
return (kmem_bufctl_t*)(slabp+1);
}
Khi cấp phát một object, kmem_cache_alloc() thực hiện công việc câp nhật dãy
kmem_bufctl (). Trường slab free giữ chỉ số của free object đầu tiên, chỉ số free object tiếp
theo là kmem_bufctl_t [slabfree] diễn giải như sau:
objp= slabp->_mem+ slabp->free*cachep-> objsize;
slabp->free= slab_bufctl (slabp) [slabp-> free];
2.4 Tính toán số object trong 1 Slab:
Trong quá trình tạo cache, hàm cache_ estimate() được gọi để tính xem có bao nhiêu
object được lưu trên 1 slab, tính trong 2 trường hợp slab descriptor được lưu trong hay ngoài slab
và kích cỡ của kmem_bufctl_t cần được track nếu 1 object là rỗi hay không. Nó trả lại số object
có thể chứa được và số byte lãng phí. Số byte lãng phí có ý nghĩa quan trọng nếu cache coloring
được sử dụng. Tính toán này được thực hiện theo những bước sau:
.Khởi tạo số byte lãng phí bằng tổng kích cỡ slab, nghĩa là PAGE_SIZE
gfporder
. Trừ đi lượng không gian yêu cầu để lưu slab descriptor.
.Đếm số lượng object có thể được lưu bao gồm kích thước của kmem_bufctl_t nếu slab
descriptor được lưu ở trong slab. Tiếp tục tăng kích cỡ cho tới khi slab được điền đầy
. Trả lại số object và số byte lãng phí
2.5 Slab Destroying:
16
Khi một cache bị shring hay destroy, slab bị delete. Bởi vị object có thể có hàm hủy
(destructor) hàm này phải được gọi. Các công việc để delete một slab như sau:
. Nếu có thể, gọi hàm destructor cho mọi object trong slab
. Nếu debug được enable , kiểm tra vết red_zone và poison
. Giải phóng page của slab sử dụng
17
3. Object:
3.1. Khởi tạo object trong slab:
Khi slab được tạo ra, tất cả object bên trog nó được đặt ở trong một trạng thái khởi tạo.
Nếu hàm khởi tạo (constructor) sử dụng được, nó được gọi cho mỗi object và người ta mong
muốn object được giữ ở trạng thái khởi tạo lúc giải phóng.
3.2. Cấp phát object:
Hàm kmem_cache_alloc() có nhiệm vụ cấp phát một object đến caller, điều này có đôi
chút khác biệt trong trường hợp đơn xử lí (UP) và đa xử lí (SMP).
Có 4 bước cơ bản. Bước đầu tiên là kiểm tra cơ bản để đảm bảo rằng sự cấp phát được
cho phép. Bước thứ hai là lựa chọn danh sách slab nào để cấp phát. Danh sách này có thể là
slabs_partial hoặc slabs_free. Nếu slabs_free không còn bất cứ slab (hoặc object), cache growing
để tạo ra slab mới trong danh sách slabs_free. Bước cuối cùng là cấp phát object từ slab đã chọn.
Trong trường hợp SMP, ta thực hiện thêm một bước nữa. Trước khi cấp phát một object,
slab allocator sẽ kiểm tra xem nếu có một object có thể sử dụng tu per-CPU cache và sẽ sử dụng
nếu có. Nếu không có, nó sẽ cấp phát một số batchcount các object trong một khối và đặt chúng
vào per-CPU cache của nó.
3.3. Giải phóng object:
Kmem cache free() được sử dụng để giải phóng object và tác vụ của nó tương đối đơn
giản. Giống như kmem_cache_alloc(), hoạt động của kmem_cache_free() khác nhau trong hai
trường hợp UP và SMP. Sự khác nhau cơ bản giữa hai trường hợp là ở chỗ, trong trường hợp của
UP, object được trả lại trực tiếp cho slab nhưng trong trường hợp của SMP, object được trả lại
cho per-CPU cache. Trong cả hai trường hợp, hàm hủy (destructor) cho object sẽ được gọi nếu
có thể. Hàm hủy có nhiệm vụ trả object về trạng thái khởi tạo.
4. Per-CPU object cache:
Một trong những tác vụ mà slab allocator đảm nhiệm là tăng hiệu suất sử dụng cache
cứng. Mục đích của việc tính toán hiêụ năng cao nhìn chung là sử dụng dữ liệu trên cùng một
CPU lâu nhất có thể. Linux thưc hiên điều này bằng cách cố gắng giữ object trong cùng một
CPU cache với per-CPU object cache, đơn giản được gọi là cpu cache cho mỗi cpu trên hệ
thống. Khi cấp phát hay giải phóng object, chúng được đặt ở cpu cache. Khi không có object nào
được giải phóng, một nhóm object được đặt vào trong pool. Khi pool trở nên quá lớn, một nửa bị
loại và đặt vào global cache. Với cách này, cache cứng trên cùng một CPU sẽ được sử dụng lâu
nhất có thể. Lợi ích thứ hai cưa phương pháp này là không phải sử dụng spinlock khi CPU truy
cập pool bởi vì CPU sẽ không thể truy cập được dữ liệu cục bộ khi object đã được gán cho CPU
này.
18
4.1. Cho phép CPU cache:
Khi một cache được tạo ra, CPU cache của nó phải được kích hoạt và bộ nhớ được cấp
phát cho nó sử dụng kmalloc(). Hàm enable_cpucache() có trách nhiệm cho việc quyết định kích
cỡ cache và gọi hàm do_tune_cpucache() để cấp phát bộ nhớ cho nó.
Tất nhiên, một CPU cache không thể tồn tại cho đến khi một size cache được chỉ định, vì
vậy biến toàn cục g_cpu_cache_up được sử dụng để gắn CPU cache được cho phép quá sớm.
Sau khi CPU cache đươc thiết lập, nó có thể được truy cập mà không phải lock vì một CPU
không bao giờ truy cập sai cpucache
4.2. Cập nhật thông tin mỗi CPU:
Khi cache mối CPU được tạo ra hay thay đổi, mỗi CPU đươc thông báo bởi một IPI. Nớ
sẽ không thể thay đổi tất cả giá trị trong cache descriptor bởi vì điều này dẫn đến vấn đề nhất
quán cache và phải dùng spinlock để bảo vệ CPU cache. Thay vào đó một cấu trúc
ccupdate_struct được định nghĩa với tất cả thông tin mà mỗi CPU cần, và mỗi CPU swap dữ liệu
mới với thông tin cũ trong cache descriptor. Cấu trúc được định nghĩa như sau:
Struct ccupdate_struct
{
Struct kmem_cache *cachep;
Struc aray_cache *new[NR_CPUS];
};
Cachep là cache được update và new là dãy các CPU descriptor cho mỗi CPU trên hệ
thống.
Sau khi thông tin đã đươc swap, dữ liệu cũ bị xóa đi.
4.3. Drainning a Per-CPU cache:
Khi một cache bị shink, bước đầu tiên là drain cpucache cho bất cứ object nào cache có
bằng cách gọi hàm drain_cpu_caches(). Điều này rất quan trọng bởi vì nếu chỉ có 1 object trong
slab được đặt vào per-cpu cache, toàn bộ slab không thể được giải phóng.
4.4. Khởi tạo Slab allocator:
Khi slab allocator tạo một cache mới, nó cấp phát cache từ cache_cache hoặc từ
kmem_cache cache, cache_cache phải được tạo như sau:
Static kmem_cache_t cache_cache =
{ 1 slab_full: LIST_HEAD_INIT (cache_cache.slabs_full),
19
2 slab_partial: LIST_HEAD_INIT (cache_cache.slabs_partial),
3 slab_frê: LIST_HEAD_INIT (cache_cache.slabs_free),
4 objsize: sizeof (kmem_cache_t),
5 flag: SLAB_NO_REAP,
6 spinlock: SPIN_LOCK_UNLOCKED,
7 colour_of: L1_CACHE_BYTES,
8 name: “kmem_cache”,
};
1-3: Khởi tạo 3 danh sách slabs_full, slabs_partial, slabs_free rỗng
4: Kích cỡ của object là kích cỡ của cache descriptor
7: Chuyển object đến L1 cache
5. Giao tiếp với Buddy allocator:
Slab allocator phải yêu cầu bộ cấp phát page vật lí cho page của nó. Hai API được cung
cấp thực hiện tác vụ này la kmem_getpages() và kmem_freepages(). Chùng chỉ vỏ ngoài của các
API buddy allocator vì vậy phải tính đến các cờ được dùng để cấp phát. Những cờ mặc định
được dùng để cấp phát lấy từ cachep-> gfpflags, và vị trí được lấy từ cachep -> gfporder trong đó
cachep yêu cầu cấp phát page. Khi giải phóng page, ClearPageSlab () sẽ được gọi cho mọi page
đươc giải phóng trước khi gọi free_pages ().
III. Triển khai.
1. Giới thiệu thư mục/proc/sys.
Nhứng file mà người sử dụng quan sát thấy ở thư mục này thực chất là những biến
kernel. Với mỗi biến, kernel có thể định nghĩa:
• Nơi đặt của nó trong / proc/ sys. Những biến liên quan đến cùng thành phần và thuộc tính
của kernel thường được đặt chung trong một thư mục.
Ví dụ, trong / proc/ sys/ net/ ipv4, ta có thể tìm thấy những file liên quan đến Ipv4.
20
• Tên của nó. Phần lớn, các file được đặt tên đơn giản giống với tên biến của kernel, nhưng
thỉnh thoảng tên của chúng được thay đổi một chút cho thân thiện hơn.
• Quyền truy cập flie. Ví dụ, 1 file có thể cho mọi người đều đọc được nhưng chỉ cho root
có quyền thay đổi.
Nội dung của những biến được xuất ra / proc/ sys có thể đọc hoặc ghi bằng cách truy cập
các file liên quan (khi ta có được cấp quyền) và trực tiếp gọi đến system call sysctl.
Những thư mục và file được xác định vào lúc khởi động, một vài file khác được thêm vào
lúc chạy. Những sự kiện dẫn đến sự tạo ra file và thư mục lúc chạy là:
- Khi module kernel thực thi 1 tính năng mới hoặc 1 module được load hay unload.
- Khi 1 device driver được đăng kí (register) hoặc bị loại bỏ (unregister). Có những tham
số cấu hình mà chỉ có 1 thể hiện mỗi device. Ví dụ, /proc/ sys/ net/ipv4/ neigh có một thư mục
con cho mỗi network device.
Cả file và thư mục trong / proc/ sys được định nghĩa với cấu trúcctl_table. Cấu trúc này
được đăng kí và bị xóa (register và unregister) với hàm register_sysctl_table và
unregister_sysctl_table, được dịnh nghĩa trong kernel/ sysctl.c.
Cá trường quan trọng của ctl_data:
Const char *procname
Tên file được sử dụng trong/ proc/ sys
int maxlen
Kích cỡ của biến kernel được export.
Mode_ t mode
Quyền được gán đến file hoặc trong thư mục.
Ctl_table* child
Được sử dụng để xây dựng mối quan hệ cha – con giữa thư mục và file.
Proc_handler
Hàm này thi hành hoạt động đọc hoặc ghi khi ta đọc hoặc ghi từ /proc/sic. Tất cả các thể
hiện clt_ có liên quan đến file phải khởi tạo proc_handler.
Strategy
21
Hàm mà được tùy chọn khởi tạo đến 1 thủ tục thực thi thêm việc định dạng dữ liệu trước
khi hiển thi hoặc lưu nó. Nó được gọi khi file trong /proc/sys được truy cập với một system call
sysctl.
Extra 1
Extra 2
Hàm tham số tùy chọn thường sử dụng để xác định giá trị nhỏ nhất và lớn nhất cho biến.
Đăng kí một file trong /proc/sys.
Ta thấy rằng 1 file có thể được đăng kí và loại khỏi /proc/sys với 2 hàm register_table và
unregister_systl tương ứng. Hàm đăng kí, yêu cầu 2 tham số đầu vào:
• Một con trỏ đến thể hiện của ctl_table
• Một cờ cho biết khi nào đặt phần tử này vào 1 danh sách ctl_table ở trong cùng một thư
mục:
ở đầu (1) hoặc ở cuối (0)
chú ý rằng đầu vào của register_sysctl_table không bao gồm tham chiếu đến vị trí ở trong
hệ thống file/proc/sys khi đầu vào ctl_table được thêm vào. Lý do là ở chỗ những thư mục được
chèn vào đều được đưa vào thư mục /proc/sys. Nếu ta muốn đăng kí một file ở trong thư mục
con của /proc/sys, ta cần cung cấp đường dẫn đầy đủ bằng cách xây dựng cây và đưa đến hàm
register_sysctl_table thể hiện ctl_table mà biểu diễn gốc của cây. Khi bất kì node nào của cây
chưa tồn tại, chúng được tạo ra.
2. Linux Modules
Mặc dù mang tính chất “monolithic” (nguyên khôi) khi toàn bộ kernel chạy trong một
protecion domain (miền bảo vệ) đơn nhưng linux kernel có tính chất module. Điều này cho
người lập trình có thể thêm vào hay bớt đi code từ kerel một cách linh động và ngay cả khi
kernel vẫn đang chạy. Các chương trình con, dữ liệu và thực thể liên quan cùng với các điểm tồn
tại được nhóm lại với nhau trong một ảnh nhị phân đơn lẻ, một thực thể kernel có khả năng nạp
được vào hệ thống thì được gọi là Module.
Việc hộ trợ khả năng sử dụng modules khiến hệ thống chỉ cần có tối thiểu một ảnh kernel
cơ bản với những tính chất và driver tùy chọn. Với điểm dễ dàng thêm và bớt khỏi mã kernel nên
midule làm tăng khả năng debug (gỡ rối) cho hệ thống , đồng thời cho phép nạp thêm vào những
drive mới để hệ thống đáp ứng được đòi hỏi cắm nóng thiết bị (hot plugging).
22
2.1 Xây dựng Module
Từ Version 2.6 trở đi, việc xây dựng module đã trở nên dễ dàng hơn, nhờ có hệ thống
“kbuild”.Điều đầu tiên khi xây dựng một module là quyết định xem mã nguồn của module sẽ
chạy ở đâu. Bạn có thể thêm module vào kernel hợp lý dưới dạng patch hoặc bằng cách đưa code
của bạn vào cây chính (official tree). Ngoài ra, bạn cũng có thể xây dựng và duy trì module bên
ngoài cây nguồn (source tree).
At home in the source tree
Khi xây dựng module, nếu bạn có thể đưa module của bạn vào như một phần chính thức
của Linux và nó tồn tại trong cây nguồn kernel thì đó là một cách làm lý tưởng, việc này đòi hỏi
một số thao tác chuẩn bị ban đầu nhưng nó vẫn thường là phương pháp hay được dùng.
Bước đầu tiên là việc quyết định xem bạn sẽ để module của mình nằm ở đâu trong cây
nguồn kernle. Bên trong thư mục này, chúng được chia ra hơn nữa dựa trên lớp, kiểu và thậm chí
là theo từng drive cụ thể. Các thiết bị hướng kí hiệu được đặt trong thư mục drives/char trong
thiết bị hướng block được đặt trong drivers/block, thiết bị USB được đặt trong thư mục:
drive/usb/. Những nguyên tắc đặt vị trí này không quá cứng nhắc bởi vì nhiều thiết bị USB cũng
đồng thời là thiết bị hướng kí hiệu, nhưng cuối cùng, thì việc tổ chức thư mục này cũng tương
đối dễ hiểu và chính xác.
Giả sử rằng bạn có một thiết bị hướng kí hiệu và muốn đặt nó vào trong thư mục
drivers/char. Bên trong thư mục này là hàng loạt các file mã nguồn được viết với ngôn ngữ C và
một vài thư mục khác. Drivers với chỉ một hoặc hai file nguồn có thể được đặt dễ dàng vào trong
thư mục này. Drivers với nhiều nguồn và các junk đi kèm sẽ có thể được đặt trong một thư mục
con mới, nguyên tắc này cũng có tính linh động chứ không quá cứng nhắc.
Giả sử rằng bạn muốn tạo ra thư mục con cho riêng mình, theo cách này, drivers của bạn
được dùng cho một fishing pole với một giao diện máy tính, Fish Master XL 2000 Titanium, vì
vậy, bạn cần tạo một thư mục con dạng fishing bên trong thư mục drivers/char/.
Việc cần làm là bạn phải thêm một vài dòng vào Makefile trong thư mục drivers/char/. Cụ thể,
bạn mở file drivers/char/makefile và thêm vào dòng sau:
Obj-m + =fishing
Việc này sẽ khiến cho hệ thống đi xuống thư mục con fishing/ bất cứ khi nào nó biên dịch
modules. Khả năng cao hơn là việc biên dịch driver của banj sẽ tiếp liên đến một tùy chọn cấu
hình cụ thể, ví dụ: CONFIG_FISHING_POLE. Trong trường hợp đó, phải cần thêm vào dòng
như sau trong makefile:
23
Obj-$(CONFIG_FISHING_POLE) +=fishing/
Cuối cùng, trong thư mục driver/char/fishing/, tạo một Makefile mới và thêm dòng sau vào:
Obj-m+=fishing.o
Bây giờ hệ thống sẽ truy cập vào thư mục fishing/ và xây dựng module fishing. Ko từ file
fishing.c
Chú ý: bạn viết ra một file với đuôi mở rộng là .o nhưng module sẽ biên dịch như file .ko
Trong trường hợp, nếu như việc biên dịch fishing pole driver của bạn phải đi kèm với
một tùy chọn cấu hình thì bạn cần thêm dòng lệnh sau trong Makefile:
Obj-$(CONFIG_FISHING_POLE) +=fishing.o
Một lúc nào đó, fishing pole driver của bạn sẽ trở nên quá phức tạp, bạn cần đến nhiều hơn một
file nguồn, bạn chỉ cần thêm dòng sau vào trong Makefile.
Obj-$(CONFIG_FISHING_POLE) +=fishing.o
Fishing-objs: = fishing-main.o fishing-line.o
Lúc này, file fishing-main.c và fishing-line.c sẽ được biên dịch và liên kết đến fishing.ko. Cuối
cùng, bạn cần đặt các cờ biên dịch gcc trong quá trình xây dựng file của bạn, cụ thể, them dòng
sau vào trong Makefile:
EXTRA_CFCLASS += - DTITANIUM_POLE
Nếu bạn chọn đặt file nguồn của mình vào trong drivers/char/ và không tạo ra thư mục
mới, hầu như bạn chỉ cần đặt các dòng phía trên (mà bạn đặt trong Makefile trong thư mục
drivers/char/fishing/) vào trong thư mục driver/ char/ Makefile.
Để biên dịch, chạy quá trình buil của kernel như thường lệ, nếu như việc dựng module của bạn là
có điều kiện với một tùy chọn cấu hình, như đã cấu hình trong thông số
CONFIG_FISHING_POLE, đảm bảo rằng các tùy chọn này được kích họat trước khi bắt đầu
quá trình tạo module.
Living Externally
Nếu bạn mong muốn xây dựng và duy trì module của mình bên ngòai cây nguồn kernel,
để hoạt động như một thành phần bên ngòai, bạn cần tạo một Makefile trong thư mục mã nguồn
của riêng bạn với một dòng như sau:
Obj-m: fishing.o
Fishing-obs: =fishing-main.o fishing-line.o
24
Thao tác này sẽ được biên dịch file fishing –main.c và fishing-line.c thanh fishing.ko
Sự khác biệt cơ bản của phương pháp này nằm ở quá trình xây dựng (build), bởi vì
module được tạo ra sẽ nằm ngòai cây kernel nên bạn cần chỉ cho lệnh make tìm đến các file
nguồn của kernel và Makefile, để làm được điều này, bạn sử dụng lênh sau:
Make –c/kernel/source/location SUBDIRS=$PWD module
Trong đó, kernel/source/location là cây nguồn kernel đã được cấu hình của bạn.
Chú ý rằng, bạn không được đặt một bản copy của cây nguồn kernel đang chạy vào trong thư
mục /usr/src/linux mà phải đặt vào một nơi nào khác, dễ dàng truy cập đến, ví dụ như thư mục
nhà của bạn (home directory)
2.2 Exported Symbols
Khi modules được load, chúng sẽ liên kết động đến nhâ. Với không gian người dung, mã
nhị phân được liên kết động chỉ có thể gọi đến hàm chức năng bên ngoài mà đã được xuất ra một
cách chính xác để dung, còn trong không gian kernel, việc này được xử lý thông qua các hàm
đặc biệt như: EXPORT_SYMBOLO và EXPORT_SYMBOL_GPLO.
Các hàm được xuất ra sẽ sẵn có để dung thông qua modules còn các hàm không được
xuất ra sẽ không thể gọi từ modules. Quy tắc liên kết đến và gọi đòi hỏi mức độ nghiêm ngặt hơn
rất nhiều so với code trong ảnh kernel lõi. Code của lõi kernel có thể gọi bất cứ giao diện động
nào trong kernel bởi vì tất cả các file nguồn của core đều đã được liên kết đến một ảnh cơ sở
duy nhất. Do đó, các kí tự được xuất ra (Exported Symbols) cũng không được tĩnh.
Tập hợp các kí hiệu của kernel mà được xuất ra được biết đến như: exported kernel
interfaces hay là Kernel API.
Quá trình xuất ra một kí hiệu khá đơn giản: sau khi hàm được khai báo, nó thường được
theo sau bởi một hàm EXPORT_SYMBOLO. Ví dụ:
/*
get_pirate_beard_color – return the color of the current pirate’s beard.
Pirate is a global variable accessible to this functinon.
25