Triển khai container_of trong C
Mở đầu
container_of
là một macro được dùng trong linux kernel.
Để có thể hiểu về contaier_of
, trước hết cần phải hiểu offsetof()
trong thư viện stddef.h
Thế offsetof()
: dùng để làm gì ?
- Macro này sẽ trả về 1 giá trị kiểu
size_t
. - Giá trị này là khoảng cách các byte của member trong struct.
Hãy xem ví dụ sau đây để có thể hiểu rõ hơn về cách sử dụng offsetof()
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
struct Register{
uint8_t setup[2];
uint8_t input;
uint8_t output[3];
};
int main()
{
printf("setup offset: %d\n", offsetof(struct Register, setup));
printf("input offset: %d\n", offsetof(struct Register, input));
printf("output offset: %d\n", offsetof(struct Register, output));
printf("Size cua struct Register: %d\n", sizeof(struct Register));
}
Output
setup offset: 0
input offset: 2
output offset: 3
Size cua struct Register: 6
Như có thể thấy kết quả ở đây, thì
setup
nằm ở vị trí đầu tiên, nênoffset
so vớistruct Register
sẽ là 0.input
nằm ở vị trí thứ 2, vàoffset
so vớistruct Register
là 2. Câu hỏi: "Tại sao lại là 2 mà không phải là giá trị nào khác ?"- Bởi vì 2 byte đầu tiên do,
setup
đã chiếm trongstruct Register
nêninput
nằm ở vị trí byte thứ 3.
- Bởi vì 2 byte đầu tiên do,
- Tương tự,
output
cóoffset
= 3 , chiếm vị trí bắt đầu ở byte 4.
Tại vì sao, tôi lại tìm hiểu vấn đề này ?
Trong quá trình phát triển 1 os theo kiến trúc event-driven
, os này dành cho dự án IoT cá nhân của tôi. Có 1 vấn đề xảy ra, cùng không hẳng là vấn đề hihi.
Mô tả như sau:
Tôi có 1 list gọi là Ready list - chứa những task sẵn sàng chạy
Ở đây tôi dùng Double linked list để lưu trữ những task ready này.
struct Dlist
{
DNode first;
DNode last;
size;
}
struct DNode
{
DNode next;
DNode prev;
}
struct Task
{
DNode Node_task; // đại diện task trong ready list
int data;
....
}
Đây là một đoạn mã đơn giãn để tôi có thể lưu trữ các task này vào DList, nhưng 1 vấn đề là khi mà ta lưu trữ task dưới dạng node trong list thì list đó sẽ không nhận diện được task nào đang nắm giữ node đó, mặc dù node có ở trong list.
- Lúc này ta cần phải tạo 1 con trỏ để có thể trỏ tới task đang nắm giữa node này
Lúc này, cần phải chỉnh sửa struct DNode
một tý
struct DNode
{
struct Task* Qwner;
DNode next;
DNode prev;
}
Lúc này thì con trỏ Qwner
sẽ trỏ tới task dang nắm giữ node này. OK => Thế là vấn đề trên đã được giải quyết.
Nhưng chưa dùng lại ở đó, tôi đã tìm hiểu được 1 pp hay hơn, không cần phải thêm con trỏ vào struct như trên => Tiết kiệm được 1 phần memory.
Đó chỉnh là sử dụng container_of
Khái niệm: dùng địa chỉ của member trong struct để có thể tính ra được địa chỉ của struct đang nắm giữ member này
Theo ví dụ trên: ta sẽ dựa vào dịa chỉ của DNode chứa trong struct Task, để có thể tìm ra dược địa chỉ của Task nắm giữa node này.
Triển khai container_of
#define container_of(ptr_member, type, member) \
((type *)((char *)(ptr_member) - offsetof(type, member)))
Trong đó:
ptr_member
: con trỏ tới member trong structtype
: kiểu struct chamember
" tên của memberoffsetof
: độ lệch của member so với struct
Để có thể hiểu các triển khai trên thì cần phải hiểu cách mà có thể truy cập member của 1 struct thông qua địa chỉ
struct Register{
uint8_t setup[2];
uint8_t input;
uint8_t output[3];
};
- Địa chỉ
setup
= địa chỉ của struct + offsetof(setup) =>setup
= địa chỉ của struct + 0 - Địa chỉ
input
= địa chỉ của struct + offsetof(input) =>input
= địa chỉ của struct + 2 - Địa chỉ
output
= địa chỉ của struct + offsetof(output) =>output
= địa chỉ của struct + 3
Qua 3 cách tính, kết luận được: Địa chỉ member = Địa chỉ struct + offsetof(member) = > Địa chỉ struct = Địa chỉ member - offsetof(member)
Nhưng hãy chú ý, nếu theo công thức trên thì macro phải như thế này
#define container_of(ptr_member, type, member) \
((type *)((ptr_member) - offsetof(type, member)))
Vậy mục đích char *
ở đây để làm gì ?
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
int main()
{
int arr[10];
int* p = arr;
// giả sử muôn lùi lại 1 byte
int* q = p - 1;
printf("Địa chỉ p đang trỏ tới: %p\n", p);
printf("Địa chỉ q đang trỏ tới: %p\n", q);
}
// OUTPUT
Địa chỉ p đang trỏ tới: 0x7ffea0128ec0
Địa chỉ q đang trỏ tới: 0x7ffea0128ebc
=> 0x7f...c0 - 0x7f...bc = 4 byte
Điều này không mong đợi, do tôi muốn lùi 1 byte
địa chỉ thôi
Cần phải ép con trỏ về kiểu dữ liệu nào mà có 1 byte
như char
, uint8_t
, ... Nếu không thì nó sẽ dịch chuyển bằng đúng với kích thước của kiểu dữ liệu của con trỏ trong hệ thống.
char* q = (char *)p - 1;
Output
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
struct Register{
uint8_t setup[2];
uint8_t input;
uint8_t output[3];
};
int main()
{
int arr[10];
int* p = arr;
// giả sử muôn lùi lại 1 byte
char* q = (char*)p - 1;
printf("Địa chỉ p đang trỏ tới: %p\n", p);
printf("Địa chỉ q đang trỏ tới: %p\n", q);
// OUTPUT
Địa chỉ p đang trỏ tới: 0x7ffe0350bce0
Địa chỉ q đang trỏ tới: 0x7ffe0350bcdf
-> 0x7f...e0 - 0x7f...df = 1 byte
}
Example
Tìm địa chỉ của struct json
thông qua id
.
#include <stdio.h>
#include <stddef.h>
#define container_of(ptr_member, type, member) \
((type *)((char *)(ptr_member) - offsetof(type, member)))
struct json{
int data;
int id;
char name[5];
};
int main()
{
struct json JSON;
struct json *p;
p = container_of(&JSON.id, struct json, id);
printf("địa chỉ JSON - p: %p\n", p);
printf("địa chỉ struct: %p\n", &JSON);
}
OUTPUT
địa chỉ JSON - p: 0x7ffe6d3f5fb0
địa chỉ struct: 0x7ffe6d3f5fb0
All rights reserved