Cấu trúc cơ bản và vòng đời của Linux Kernel Module
Module kernel là một đoạn mã có thể tải động vào kernel space khi hệ thống đang chạy, không cần biên dịch lại toàn bộ kernel. Điều này cho phép mở rộng chức năng hệ thống linh hoạt.
Một module bắt buộc phải có hai hàm chính: init (khởi tạo) và exit (giải phóng). Hàm init chạy khi module được tải vào (insmod), còn hàm exit chạy khi module được gỡ ra (rmmod).
Viết file nguồn module mẫu (hello.c)
File này định nghĩa cấu trúc dữ liệu cơ bản và hai hàm lifecycle. Chúng ta sử dụng các API printk để ghi log vào vòng đệm kernel (kernel ring buffer) để kiểm tra trạng thái.
Tạo file /usr/src/hello/hello.c với nội dung hoàn chỉnh:
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello Module: Initialized successfully.\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Hello Module: Cleaning up resources.\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SysAdmin Team");
MODULE_DESCRIPTION("Basic Kernel Module for Distributed DB Series");
Kết quả mong đợi: Khi biên dịch, file object (.ko) sẽ được tạo mà không có lỗi liên quan đến hàm init/exit. Dòng MODULE_LICENSE("GPL") là bắt buộc để kernel chấp nhận module không có chữ ký.
File Kbuild để biên dịch module
Kernel sử dụng hệ thống build riêng biệt dựa trên Makefile. File Makefile (hoặc Kbuild tùy ngữ cảnh) hướng dẫn trình biên dịch tìm nguồn và liên kết với kernel headers hiện hành.
Tạo file /usr/src/hello/Makefile với nội dung:
obj-m += hello.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
\tcd $(KDIR) && make M=$(PWD) modules
clean:
\tcd $(KDIR) && make M=$(PWD) clean
Kết quả mong đợi: Chạy lệnh make trong thư mục này sẽ tạo ra file hello.ko tương thích với kernel đang chạy của bạn.
Biên dịch và kiểm tra module
Thực hiện biên dịch module và đưa vào kernel space để xác nhận vòng đời hoạt động đúng.
cd /usr/src/hello
make
sudo insmod hello.ko
sudo dmesg | tail -n 5
Kết quả mong đợi: Dòng cuối cùng của dmesg sẽ hiển thị thông báo: [timestamp] Hello Module: Initialized successfully.
sudo rmmod hello
sudo dmesg | tail -n 2
Kết quả mong đợi: Dòng cuối cùng sẽ hiển thị: [timestamp] Hello Module: Cleaning up resources.. Nếu không thấy dòng này, module chưa được gỡ đúng cách hoặc bị lỗi giữ tài nguyên.
Cấu trúc thư mục và quy trình Build cho Kernel Module
Để quản lý code module trong dự án phân tán, cần chuẩn hóa cấu trúc thư mục. Điều này giúp tách biệt source code, build artifacts và các file cấu hình.
Cấu trúc thư mục chuẩn bao gồm: thư mục chứa source code, thư mục chứa file build (Makefile), và thư mục tạm cho các file object.
Thiết lập cây thư mục chuẩn
Tạo cấu trúc thư mục tại /usr/src/shamir-distributed-db để chứa toàn bộ source code của series.
mkdir -p /usr/src/shamir-distributed-db/kernel-modules
cd /usr/src/shamir-distributed-db/kernel-modules
mkdir -p build src
Kết quả mong đợi: Hệ thống thư mục được tạo sẵn sàng để chứa file nguồn và file build.
Chuyển file nguồn vào thư mục src
Di chuyển file hello.c đã tạo ở phần trên vào thư mục src để chuẩn hóa vị trí.
cp /usr/src/hello/hello.c /usr/src/shamir-distributed-db/kernel-modules/src/
cp /usr/src/hello/Makefile /usr/src/shamir-distributed-db/kernel-modules/
Kết quả mong đợi: File nguồn nằm trong src/hello.c và file build nằm ở root của module directory.
Chỉnh sửa Makefile cho cấu trúc mới
Cần cập nhật Makefile để trỏ đúng đến thư mục src khi biên dịch. Biến obj-m phải trỏ đến file source trong thư mục con.
Sửa file /usr/src/shamir-distributed-db/kernel-modules/Makefile:
obj-m += src/hello.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
\tcd $(KDIR) && make M=$(PWD) modules
clean:
\tcd $(KDIR) && make M=$(PWD) clean
Kết quả mong đợi: Khi chạy make, trình biên dịch sẽ tự động tìm src/hello.c và tạo src/hello.o rồi link thành src/hello.ko.
Verify kết quả build
Thực hiện build và kiểm tra xem file .ko được tạo ở đâu.
cd /usr/src/shamir-distributed-db/kernel-modules
make
ls -lh src/*.ko
Kết quả mong đợi: File src/hello.ko xuất hiện với kích thước khoảng 10-20KB tùy kiến trúc.
Sử dụng API Kernel: Đăng ký Device và Xử lý Request
Để module Shamir tương tác với không gian người dùng (user space), chúng ta cần tạo một thiết bị đặc biệt (character device). Điều này cho phép ứng dụng user space mở file device và gửi yêu cầu (request) vào kernel.
Chúng ta sẽ sử dụng register_chrdev để đăng ký device và file_operations để định nghĩa các hành động khi user thực hiện read hoặc write.
Viết module Device Driver cơ bản (shamir_dev.c)
Module này sẽ đăng ký một character device với tên shamir_dev. Nó sẽ bắt sự kiện write để nhận dữ liệu và trả về thông báo qua printk.
Tạo file /usr/src/shamir-distributed-db/kernel-modules/src/shamir_dev.c:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
static int major_number;
static char *device_name = "shamir_dev";
static struct class *shamir_class = NULL;
static struct device *shamir_device = NULL;
// Xử lý khi user write vào device
static ssize_t shamir_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)
{
char kernel_buf[256];
if (count > 255) {
count = 255;
}
// Copy dữ liệu từ user space sang kernel space
if (copy_from_user(kernel_buf, buf, count)) {
return -EFAULT;
}
kernel_buf[count] = '\0'; // Thêm null terminator
printk(KERN_INFO "Shamir Device: Received data: %s\n", kernel_buf);
return count;
}
// Xử lý khi user read từ device
static ssize_t shamir_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
const char *msg = "Shamir Secret Sharing Active\n";
int len = strlen(msg);
if (count < len)
return -EINVAL;
if (copy_to_user(buf, msg, len)) {
return -EFAULT;
}
return len;
}
// Cấu trúc file_operations
static struct file_operations fops = {
.read = shamir_read,
.write = shamir_write,
};
static int __init shamir_init(void)
{
int ret;
// Đăng ký major number động
major_number = register_chrdev(0, device_name, &fops);
if (major_number < 0) {
printk(KERN_ALERT "Failed to register major number\n");
return major_number;
}
printk(KERN_INFO "Shamir Device: Registered with major number %d\n", major_number);
// Tạo class trong sysfs
shamir_class = class_create(THIS_MODULE, device_name);
if (IS_ERR(shamir_class)) {
unregister_chrdev(major_number, device_name);
return PTR_ERR(shamir_class);
}
// Tạo device node
shamir_device = device_create(shamir_class, NULL, MKDEV(major_number, 0), NULL, device_name);
if (IS_ERR(shamir_device)) {
class_destroy(shamir_class);
unregister_chrdev(major_number, device_name);
return PTR_ERR(shamir_device);
}
printk(KERN_INFO "Shamir Device: /dev/%s created successfully.\n", device_name);
return 0;
}
static void __exit shamir_exit(void)
{
device_destroy(shamir_class, MKDEV(major_number, 0));
class_destroy(shamir_class);
unregister_chrdev(major_number, device_name);
printk(KERN_INFO "Shamir Device: Unregistered and cleaned up.\n");
}
module_init(shamir_init);
module_exit(shamir_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SysAdmin Team");
MODULE_DESCRIPTION("Character Device Driver for Shamir Secret Sharing");
Kết quả mong đợi: Code này sử dụng register_chrdev để lấy major number động, tạo class và device node trong /dev. Hàm copy_from_user và copy_to_user đảm bảo an toàn khi truyền dữ liệu giữa user và kernel.
Cập nhật Makefile để build module mới
Cần cập nhật obj-m để biên dịch file shamir_dev.c thay vì file cũ.
Sửa file /usr/src/shamir-distributed-db/kernel-modules/Makefile:
obj-m += src/shamir_dev.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
\tcd $(KDIR) && make M=$(PWD) modules
clean:
\tcd $(KDIR) && make M=$(PWD) clean
Kết quả mong đợi: Chạy make sẽ tạo ra file src/shamir_dev.ko.
Tải module và tạo thiết bị
Thực hiện tải module vào kernel. Hệ thống sẽ tự động tạo node device tại /dev/shamir_dev.
cd /usr/src/shamir-distributed-db/kernel-modules
sudo insmod src/shamir_dev.ko
ls -l /dev/shamir_dev
Kết quả mong đợi: Xuất hiện file device /dev/shamir_dev với quyền truy cập (thường là crw-rw----). Dòng dmesg sẽ hiện thông báo "created successfully".
Test giao tiếp User Space - Kernel Space
Sử dụng lệnh echo để ghi dữ liệu vào device và cat để đọc dữ liệu trả về. Đây là cách test API write và read đã định nghĩa.
echo "Secret_Share_Data_123" | sudo tee /dev/shamir_dev
sudo dmesg | tail -n 3
Kết quả mong đợi: Dòng cuối của dmesg hiển thị: Shamir Device: Received data: Secret_Share_Data_123.
sudo cat /dev/shamir_dev
Kết quả mong đợi: Dòng lệnh trả về: Shamir Secret Sharing Active.
Gỡ module và dọn dẹp
Gỡ module để đảm bảo hàm exit được gọi và node device bị xóa.
sudo rmmod shamir_dev
ls -l /dev/shamir_dev
Kết quả mong đợi: Lệnh ls báo lỗi No such file or directory vì node đã bị xóa. Dòng dmesg hiện thông báo "Unregistered and cleaned up".
Điều hướng series:
Mục lục: Series: Triển khai Database phân tán với Shamir Secret Sharing và Linux Kernel Modules
« Phần 2: Lý thuyết nền tảng: Cơ chế chia sẻ bí mật Shamir và kiến trúc phân tán
Phần 4: Triển khai thuật toán Shamir trong không gian Kernel (User Space vs Kernel Space) »