1. Tổng quan
Linux kernel
Linux Kernel là trái tim của các hệ điều hành Linux. Nó là một phần mềm mã nguồn mở (mã nguồn có thể được sử dụng bởi bất kỳ ai một cách tự do) phổ biến nhất và được sử dụng rộng rãi.
Kernel là thành phần cốt lõi của một hệ điều hành. Kernel cung cấp một nền tảng cho các chương trình và các dịch vụ khác nhau để chúng có thể hoạt động trên nó. Linux kernel có thể sửa đổi theo nhu cầu của người dùng. Từ đó ta có khái niệm về Kernel module
Kernel module
Hệ điều hành Linux được thiết kế cho phép người sử dụng nạp thêm một đoạn mã chương trình vào bên trong kernel và trở thành một phẩn của nó. Cơ chế này được gọi là kernel module.
Các kernel module là các đoạn mã có thể được load và unload vào kernel theo yêu cầu của người dùng. Chúng mở rộng chức năng của kernel mà không cần khởi động lại hệ thống.
Có 2 phương pháp để thêm đoạn mã code trên vào Linux kernel:
- Thêm code vào kernel source và thực hiện biên dịch lại kernel
- Thêm code vào kernel source khi kernel đang chạy. Quá trình này được thực hiện bằng cách sử dụng các câu lệnh insmod và rmmod các file kernel module (đuôi .ko)
Kernel module không có hàm main. Thay vào đó, nó có 2 hàm init_module() và hàm cleanup_module()
- init_module() được gọi ra khi module được load vào kernel bằng câu lệnh “insmod”
- cleanup_module() được gọi ra khi module bị unload ra khỏi kernel bằng câu lệnh “rmmod”
Ngoài ra, chúng ta có thể tùy ý đặt tên hàm bắt đầu kết thúc một module bằng cách sử dụng macro:
1 2 3 |
module_init(tên_hàm_bắt_đầu); module_exit(tên_hàm_kết_thúc); |
2. Tạo một kernel module trên Linux (ubuntu)
Setup Environment:
Hệ thống phải được cài bộ soucre Linux kernel header để có thể build được đoạn code kernel. Linux kernel header là thành phần được sử dụng để biên dịch cho module của kernel. Kernel header được cài đặt phải trùng với kernel version trên máy tính của mình (uname –r).
Để cài đặt kernel header, ta sử dụng cú pháp:
1 |
sudo apt install linux-headers-`uname -r |
Sau khi được cài đặt, kernel header nằm trong thư mục /lib/modules/
Makefile:
Kernel module thường được compile bằng Makefile:
1 2 3 4 5 6 7 8 9 10 11 |
EXTRA_CFLAGS = -Wall obj-m = hello.o KDIR = /lib/modules/`uname -r`/build all: make -C $(KDIR) M=`pwd` modules clean: make -C $(KDIR) M=`pwd` clean |
obj-m: trỏ vào tên file code (đuôi .o)
pwd: lưu đường dẫn hiện tại, nơi chứa file output
KDIR: trỏ vào đường dẫn chứa bộ code của kernel
Sau khi Make thành công, sẽ có một file có đuôi “.ko” được tạo ra. Nó chứa mã nguồn đã được build ra.
Ví dụ hello kernel module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <linux/module.h> /* Thư viện định nghĩa các macro như module_init va module_exit */ #include <linux/kernel.h> /* hàm được gọi ra khi load module */ int init_module(void) { pr_info("Hello world driver is loaded\n"); return 0; } /* hàm được gọi ra khi unload module */ void cleanup_module(void) { pr_info("hello world driver is unloaded\n"); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Author"); MODULE_DESCRIPTION("Hello world kernel module"); |
Trong đoạn code trên, hàm pr_info() được dùng để ghi chuỗi kí tự lên log của hệ thống. Để xem log của hệ thống, ta dùng cmd “dmesg”
Sau khi dùng Makefile để complie chương trình hello.c trên, ta thu được file hello.ko chính là file kernel module.
Hàm init_module() được gọi ra khi ta sự dụng cmd “sudo insmod hello.ko”, ta thu được dòng log đúng với chuỗi string trong pr_info()
![](https://vinalinux.com.vn/wp-content/uploads/2023/11/image-33.png)
Để xem các module đang được chạy trên hệ thống, ta dùng câu lệnh “lsmod”
![](https://vinalinux.com.vn/wp-content/uploads/2023/11/image-34.png)
Tương tự với hàm cleanup_init() khi unload module với câu lệnh “sudo rmmod hello”:
![](https://vinalinux.com.vn/wp-content/uploads/2023/11/image-35.png)
Ngoài ra, các macro MODULE_LICENSE, MODULE_AUTHOR,… được sử dụng để mô tả các thông tin của module. Ta có thể xem các thông tin này qua cmd “modinfo hello.ko”:
![](https://vinalinux.com.vn/wp-content/uploads/2023/11/image-36.png)
3. Hello kernel module trên beaglebone black
Tương tự với môi trường trên Linux (x86), đoạn mã code kernel module cũng giống như với ví dụ trên:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <linux/module.h> /* Thư viện định nghĩa các macro như module_init va module_exit */ #include <linux/kernel.h> /* hàm được gọi ra khi load module */ int init_module(void) { pr_info("Hello world driver is loaded\n"); return 0; } /* hàm được gọi ra khi unload module */ void cleanup_module(void) { pr_info("hello world driver is unloaded\n"); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Author"); MODULE_DESCRIPTION("Hello world kernel module"); |
Tuy nhiên, với beaglebone black do có phần cứng khá hạn chế nên thông thường, chúng ta sẽ không thực hiện compile trực tiếp trên board. Thay vào đó sẽ sử dụng kỹ thuật cross-compile: nơi build file binary sẽ là trên máy tính x86 (ubuntu), nơi thực thi chương trình đã được compile là trên beaglebone black
Do đó, trước khi thực hiện cross-compile, chúng ta cần cài đặt các package và các công cụ cần thiện để thực hiện biên dịch cho kiến trúc ARM trên beaglebone black – gọi là toolchain.
Makefile được dùng để thực hiện việc biên dịch:
1 2 3 4 5 6 7 8 9 10 11 12 |
obj-m += hello.o PWD := $(shell pwd) CROSS=/home/loind/work/beagle_bone_black/kernelbuildscripts/dl/gcc-8.5.0-nolibc/arm-linux-gnueabi/bin/arm-linux-gnueabi- KER_DIR=/home/loind/work/beagle_bone_black/kernelbuildscripts/KERNEL all: make ARCH=arm CROSS_COMPILE=$(CROSS) -C $(KER_DIR) M=$(PWD) modules clean: make -C $(KER_DIR) M=$(PWD) clean |
ARCH: chỉ ra bộ code build cho kiến trúc gì
CROSS: trỏ vào đường dẫn của toolchain, bỏ hậu tố “gcc”
KER_DIR: trỏ vào đường dẫn chứa bộ code của kernel và chính bộ kernel này build ra image đang chạy trên beaglebone black
Khi thực hiện chạy kernel module trên beaglebone black, ta cũng thu được kết quả như đã thực hiện trên máy Linux:
![](https://vinalinux.com.vn/wp-content/uploads/2023/11/image-37.png)
Thực hiện unload kernel module:
![](https://vinalinux.com.vn/wp-content/uploads/2023/11/image-38.png)
4. Tạo kernel module bink led trên beaglebone black
Ở phần này, chúng ta cùng nhau thực hiện một ví dụ bink led trên chân GPIO31 của beaglebone black với pinout như hình sau:
![](https://vinalinux.com.vn/wp-content/uploads/2023/11/image-39.png)
Với hàm init_module() thực hiện khởi tạo chân GPIO31:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
int init_module(void) { pr_info("Hello world driver is loaded\n"); // in log dòng thông báo if (gpio_is_valid(31) == false) { pr_err("GPIO %d is not valid\n", 31); // in log thông báo lỗi return 0; } if(gpio_request(31 ,"GPIO_31") < 0) { // set chức năng cho chân 31 có chức năng là GPIO pr_err("ERROR: GPIO %d request\n", 31); return 0; } gpio_direction_output(31, 0); // set output với đầu ra ban đầu là 0 gpio_export(31, false); // không cho ứng dụng trong user thay đổi mode in or output /* Khởi tạo dữ liệu cho timer */ timer_setup(&my_timer, timer_function, 0); /* Thêm timer vào bộ lập lịch của hệ điều hành */ add_timer(&my_timer); return 0; } |
Hàm xử lý timer sẽ thực hiện bink led với khoảng thời gian 1s 1 lần:
1 2 3 4 5 6 7 8 9 10 11 12 |
static void timer_function(struct timer_list *t) { int i = 0; for (i = 10; i > 0; i--) { gpio_set_value(31, 1); msleep(1000); gpio_set_value(31, 0); msleep(1000); } } |
Hàm cleanup_module() thực hiện giải phóng timer đã khởi tạo ra khỏi hệ thống khi không sử dụng:
1 2 3 4 5 6 |
void cleanup_module(void) { del_timer_sync(&my_timer); pr_info("hello world driver is unloaded\n"); } |
Ví dụ chương trình kernel module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#include <linux/module.h> #include <linux/kernel.h> #include <linux/time.h> #include <linux/io.h> #include <linux/delay.h> #include <linux/timer.h> #include <linux/slab.h> #include <linux/gpio.h> struct timer_list my_timer; // struct timer do hệ điều hành tạo ra /* Hàm xử lý timer */ static void timer_function(struct timer_list *t) { int i = 0; for (i = 10; i > 0; i--) { gpio_set_value(31, 1); msleep(1000); gpio_set_value(31, 0); msleep(1000); } } int init_module(void) { pr_info("Hello world driver is loaded\n"); if (gpio_is_valid(31) == false) // kiểm tra khả năng sử dụng được của chân 31 { pr_err("GPIO %d is not valid\n", 31); return 0; } if(gpio_request(31 ,"GPIO_31") < 0) { // set chức năng cho chân 31 có chức năng là GPIO pr_err("ERROR: GPIO %d request\n", 31); return 0; } gpio_direction_output(31, 0); // set output với đầu ra ban đầu là 0 gpio_export(31, false); // không cho ứng dụng trong user thay đổi mode in or output /* Khởi tạo dữ liệu cho timer */ timer_setup(&my_timer, timer_function, 0); /* Thêm timer vào bộ lập lịch của hệ điều hành */ add_timer(&my_timer); return 0; } void cleanup_module(void) { del_timer_sync(&my_timer); pr_info("hello world driver is unloaded\n"); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ta Manh Tuyen"); MODULE_DESCRIPTION("GPIO led kernel module"); |