Thực nghiệm về Đồng thời và Cạnh tranh

Phần 1: Thực nghiệm với thao tác nguyên tử

Phần này sử dụng thao tác nguyên tử để thực hiện truy cập độc quyền thiết bị LED, đảm bảo chỉ một ứng dụng có thể sử dụng LED tại một thời điểm.

1.1 Viết chương trình thực nghiệm

Vì đã sửa cây thiết bị trong chương trình 12, chúng ta không cần sửa lại ở đây.

Tạo thư mục con 7_atomic trong /linux/atk-mpl/Drivers, sao chép file gpioled.c từ thư mục 5_gpioled vào thư mục 7_atomic và đổi tên thành atomic.c. Tạo cũng một vùng làm việc Vscode trong thư mục này. Trước tiên, viết chương trình atomic.c:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDDEV_CNT			1		  	/* Số lượng thiết bị */
#define LEDDEV_NAME		"leddevice"	/* Tên thiết bị */
#define LEDOFF 				0			/* Tắt đèn */
#define LEDON 				1			/* Bật đèn */

/* Cấu trúc thiết bị led */
struct led_device{
	dev_t device_id;			/* ID thiết bị */
	struct cdev cdev;		/* cdev */
	struct class *device_class;	/* Lớp */
	struct device *device;	/* Thiết bị */
	int major_num;				/* Số thiết bị chính */
	int minor_num;				/* Số thiết bị phụ */
	struct device_node	*node; /* Node thiết bị */
	int led_pin;			/* Số GPIO cho LED */
	atomic_t lock_status; 	// Biến nguyên tử
};

struct led_device led_device;	/* Thiết bị led */

/*
 * @description		: Mở thiết bị
 * @param - inode 	: inode truyền vào driver
 * @param - filp 	: File thiết bị, cấu trúc file có thành viên private_data
 * 					  Thường trong hàm open, private_data sẽ trỏ đến cấu trúc thiết bị.
 * @return 			: 0 Thành công;Khác Thất bại
 */
static int device_open(struct inode *inode, struct file *filp)
{
	/* Kiểm tra xem LED có đang được sử dụng bởi ứng dụng khác không */
	if (!atomic_dec_and_test(&led_device.lock_status))
	{
		atomic_inc(&led_device.lock_status);
		return -EBUSY;
	}
	
	filp->private_data = &led_device; /* Thiết lập dữ liệu riêng */
	return 0;
}

/*
 * @description		: Đọc dữ liệu từ thiết bị 
 * @param - filp 	: File thiết bị cần mở (file descriptor)
 * @param - buf 	: Bộ đệm dữ liệu trả về cho không gian người dùng
 * @param - cnt 	: Độ dài dữ liệu cần đọc
 * @param - offt 	: Độ lệch so với địa chỉ bắt đầu file
 * @return 			: Số byte đọc được, nếu là giá trị âm, nghĩa là đọc thất bại
 */
static ssize_t device_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: Ghi dữ liệu vào thiết bị 
 * @param - filp 	: File thiết bị, biểu thị file descriptor được mở
 * @param - buf 	: Dữ liệu cần ghi vào thiết bị
 * @param - cnt 	: Độ dài dữ liệu cần ghi
 * @param - offt 	: Độ lệch so với địa chỉ bắt đầu file
 * @return 			: Số byte ghi được, nếu là giá trị âm, nghĩa là ghi thất bại
 */
static ssize_t device_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int result;
	unsigned char data_buffer[1];
	unsigned char led_state;
	struct led_device *dev = filp->private_data;

	result = copy_from_user(data_buffer, buf, cnt);
	if(result < 0) {
		printk("Ghi kernel thất bại!\r\n");
		return -EFAULT;
	}

	led_state = data_buffer[0];

	if(led_state == LEDON) {	
		gpio_set_value(dev->led_pin, 0);	/* Bật đèn LED */
	} else if(led_state == LEDOFF) {
		gpio_set_value(dev->led_pin, 1);	/* Tắt đèn LED */
	}
	return 0;
}

/*
 * @description		: Đóng/giải phóng thiết bị
 * @param - filp 	: File thiết bị cần đóng (file descriptor)
 * @return 			: 0 Thành công;Khác Thất bại
 */
static int device_release(struct inode *inode, struct file *filp)
{
	struct led_device *dev = filp->private_data;

	atomic_inc(&dev->lock_status);

	return 0;
}

/* Hàm hoạt động của thiết bị */
static struct file_operations led_device_ops = {
	.owner = THIS_MODULE,
	.open = device_open,
	.read = device_read,
	.write = device_write,
	.release = device_release,
};

/*
 * @description	: Hàm xuất của driver
 * @param 		: Không
 * @return 		: Không
 */
static int __init device_init(void)
{
	int ret = 0;
	const char *str;

	// 1. Khởi tạo biến nguyên tử
	led_device.lock_status = (atomic_t)ATOMIC_INIT(0);

	// 2. Đặt biến nguyên tử thành 1
	atomic_set(&led_device.lock_status, 1);

	/* Thiết lập GPIO cho LED */
	/* 1. Lấy node thiết bị: led_device */
	led_device.node = of_find_node_by_path("/led_device");
	if(led_device.node == NULL) {
		printk("Không tìm thấy node led_device!\r\n");
		return -EINVAL;
	}

	/* 2. Đọc thuộc tính status */
	ret = of_property_read_string(led_device.node, "status", &str);
	if(ret < 0) 
	    return -EINVAL;

	if (strcmp(str, "okay"))
        return -EINVAL;
    
	/* 3. Lấy giá trị thuộc tính compatible và khớp */
	ret = of_property_read_string(led_device.node, "compatible", &str);
	if(ret < 0) {
		printk("Không lấy được thuộc tính compatible\r\n");
		return -EINVAL;
	}

    if (strcmp(str, "atf,led")) {
        printk("Khớp compatible thất bại\r\n");
        return -EINVAL;
    }

	/* 4. Lấy thuộc tính GPIO trong cây thiết bị, nhận số LED sử dụng */
	led_device.led_pin = of_get_named_gpio(led_device.node, "led-pin", 0);
	if(led_device.led_pin < 0) {
		printk("Không lấy được led-pin");
		return -EINVAL;
	}
	printk("Số GPIO LED = %d\r\n", led_device.led_pin);

	/* 5. Yêu cầu sử dụng GPIO từ hệ thống GPIO */
	ret = gpio_request(led_device.led_pin, "LED-PIN");
    if (ret) {
        printk(KERN_ERR "Yêu cầu GPIO LED thất bại\r\n");
        return ret;
	}

	/* 6. Thiết lập GPIO là đầu ra, mức cao, mặc định tắt LED */
	ret = gpio_direction_output(led_device.led_pin, 1);
	if(ret < 0) {
		printk("Không thiết lập được GPIO!\r\n");
	}

	/* Đăng ký driver thiết bị ký tự */
	/* 1. Tạo ID thiết bị */
	if (led_device.major_num) {
		led_device.device_id = MKDEV(led_device.major_num, 0);
		ret = register_chrdev_region(led_device.device_id, LEDDEV_CNT, LEDDEV_NAME);
		if(ret < 0) {
			pr_err("Không đăng ký được driver ký tự [ret=%d]\n", LEDDEV_CNT);
			goto free_gpio;
		}
	} else {
		ret = alloc_chrdev_region(&led_device.device_id, 0, LEDDEV_CNT, LEDDEV_NAME);
		if(ret < 0) {
			pr_err("%s Không thể alloc_chrdev_region, ret=%d\r\n", LEDDEV_NAME, ret);
			goto free_gpio;
		}
		led_device.major_num = MAJOR(led_device.device_id);
		led_device.minor_num = MINOR(led_device.device_id);
	}
	printk("Thiết bị chính=%d,phụ=%d\r\n", led_device.major_num, led_device.minor_num);	
	
	/* 2. Khởi tạo cdev */
	led_device.cdev.owner = THIS_MODULE;
	cdev_init(&led_device.cdev, &led_device_ops);
	
	/* 3. Thêm cdev */
	cdev_add(&led_device.cdev, led_device.device_id, LEDDEV_CNT);
	if(ret < 0)
		goto del_unregister;
		
	/* 4. Tạo lớp */
	led_device.device_class = class_create(THIS_MODULE, LEDDEV_NAME);
	if (IS_ERR(led_device.device_class)) {
		goto del_cdev;
	}

	/* 5. Tạo thiết bị */
	led_device.device = device_create(led_device.device_class, NULL, led_device.device_id, NULL, LEDDEV_NAME);
	if (IS_ERR(led_device.device)) {
		goto destroy_class;
	}
	return 0;
	
destroy_class:
	class_destroy(led_device.device_class);
del_cdev:
	cdev_del(&led_device.cdev);
del_unregister:
	unregister_chrdev_region(led_device.device_id, LEDDEV_CNT);
free_gpio:
	gpio_free(led_device.led_pin);
	return -EIO;
}

/*
 * @description	: Hàm thoát của driver
 * @param 		: Không
 * @return 		: Không
 */
static void __exit device_exit(void)
{
	/* Hủy đăng ký driver thiết bị ký tự */
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.device_id, LEDDEV_CNT);
	device_destroy(led_device.device_class, led_device.device_id);
	class_destroy(led_device.device_class);
	gpio_free(led_device.led_pin);
}

module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ATF");
MODULE_INFO(intree, "Y");

Tiếp theo, viết file kiểm tra atomicApp.c:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDOFF 	0
#define LEDON 	1

/*
 * @description		: Hàm main chính
 * @param - argc 	: Số phần tử mảng argv
 * @param - argv 	: Tham số cụ thể
 * @return 			: 0 Thành công;Khác Thất bại
 */
int main(int argc, char *argv[])
{
	int fd, result;
	char *filename;
	unsigned char count = 0;
	unsigned char data_buffer[1];
	
	if(argc != 3){
		printf("Cách dùng sai!\r\n");
		return -1;
	}

	filename = argv[1];

	/* Mở driver LED */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("Mở file %s thất bại!\r\n", argv[1]);
		return -1;
	}

	data_buffer[0] = atoi(argv[2]);	/* Thao tác thực hiện: bật hoặc tắt */

	/* Ghi dữ liệu vào file /dev/leddevice */
	result = write(fd, data_buffer, sizeof(data_buffer));
	if(result < 0){
		printf("Điều khiển LED thất bại!\r\n");
		close(fd);
		return -1;
	}

	/* Giả sử sử dụng LED trong 25 giây */
	while(1) {
		sleep(5);
		count++;
		printf("Số lần chạy ứng dụng:%d\r\n", count);
		if(count >= 5) break;
	}

	printf("Ứng dụng chạy xong!");
	result = close(fd);
	if(result < 0){
		printf("Đóng file %d thất bại!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

1.2 Chạy kiểm tra

Viết file Makefile:

KERNELDIR := /home/atf/linux/atk-mpl/linux/my_linux/linux-5.4.31	# Đường dẫn nguồn kernel Linux
CURRENT_PATH := $(shell pwd)

obj-m := atomic.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

Biên dịch Makefile để có file atomic.ko:

make

Biên dịch atomicApp.c để có file atomicApp:

arm-none-linux-gnueabihf-gcc atomicApp.c -o atomicApp

Sao chép hai file này:

sudo cp atomicApp atomic.ko /home/atf/linux/nfs/rootfs/lib/modules/5.4.31/

Bắt đầu board, nhập lệnh để tải driver atomic.ko:

depmod
modprobe atomic.ko

Nhập lệnh để bật LED, và cứ mỗi 5 giây sẽ in ra App running times:

./atomicApp /dev/leddevice 1&    # "&" có nghĩa là chạy atomicApp ở chế độ nền

Khi đang chạy, nhập lệnh sau:

/atomicApp /dev/leddevice 0     # Tắt đèn LED

Mở /dev/leddevice thất bại, lý do là phần mềm atomicApp đang sử dụng /dev/leddevice, nếu chạy lại atomicApp để thao tác với /dev/leddevice chắc chắn sẽ thất bại. Phải đợi atomicApp chạy xong (25 giây) thì các phần mềm khác mới có thể thao tác với /dev/leddevice. Đây chính là sử dụng biến nguyên tử để thực hiện chỉ một ứng dụng có thể truy cập LED tại một thời điểm.

Cuối cùng, gỡ driver:

rmmod atomic.ko

Phần 2: Thực nghiệm với Spinlock

Phần trước sử dụng thao tác nguyên tử để thực hiện một ứng dụng truy cập LED, lần này chúng ta sử dụng spinlock để thực hiện.

Trước tiên, lưu ý các điểm sử dụng spinlock:

① Khu vực được bảo vệ bởi spinlock cần càng ngắn càng tốt. Sử dụng một biến để biểu thị trạng thái sử dụng thiết bị, nếu thiết bị đang được sử dụng thì biến sẽ tăng 1, khi thiết bị được giải phóng thì biến giảm 1, chúng ta chỉ cần sử dụng spinlock để bảo vệ biến này.

② Xem xét khả năng tương thích của driver, chọn hàm API hợp lý.

2.1 Viết chương trình thực nghiệm

Không cần sửa cây thiết bị.

Sao chép atomic.c, Makefile, atomicApp.c từ phần trước vào thư mục con mới 8_spinlock, và đổi tên atomic thành spinlock, trước tiên sửa file spinlock.c:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDDEV_CNT			1		  	/* Số lượng thiết bị */
#define LEDDEV_NAME		"leddevice"	/* Tên thiết bị */
#define LEDOFF 				0			/* Tắt đèn */
#define LEDON 				1			/* Bật đèn */

/* Cấu trúc thiết bị led */
struct led_device{
	dev_t device_id;			/* ID thiết bị */
	struct cdev cdev;		/* cdev */
	struct class *device_class;	/* Lớp */
	struct device *device;	/* Thiết bị */
	int major_num;				/* Số thiết bị chính */
	int minor_num;				/* Số thiết bị phụ */
	struct device_node	*node; /* Node thiết bị */
	int led_pin;			/* Số GPIO cho LED */
	int device_status;		// Trạng thái sử dụng thiết bị 0-chưa sử dụng >0-đang sử dụng
	spinlock_t lock;		// Spinlock
};

struct led_device led_device;	/* Thiết bị led */

/*
 * @description		: Mở thiết bị
 * @param - inode 	: inode truyền vào driver
 * @param - filp 	: File thiết bị, cấu trúc file có thành viên private_data
 * 					  Thường trong hàm open, private_data sẽ trỏ đến cấu trúc thiết bị.
 * @return 			: 0 Thành công;Khác Thất bại
 */
static int device_open(struct inode *inode, struct file *filp)
{
	unsigned long flags;	// Biến trạng thái ngắt
	filp->private_data = &led_device;

	spin_lock_irqsave(&led_device.lock, flags);
	if (led_device.device_status)
	{
		spin_unlock_irqrestore(&led_device.lock, flags);
		return -EBUSY;
	}
	led_device.device_status++;
	spin_unlock_irqrestore(&led_device.lock, flags);

	return 0;
}

/*
 * @description		: Đọc dữ liệu từ thiết bị 
 * @param - filp 	: File thiết bị cần mở (file descriptor)
 * @param - buf 	: Bộ đệm dữ liệu trả về cho không gian người dùng
 * @param - cnt 	: Độ dài dữ liệu cần đọc
 * @param - offt 	: Độ lệch so với địa chỉ bắt đầu file
 * @return 			: Số byte đọc được, nếu là giá trị âm, nghĩa là đọc thất bại
 */
static ssize_t device_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: Ghi dữ liệu vào thiết bị 
 * @param - filp 	: File thiết bị, biểu thị file descriptor được mở
 * @param - buf 	: Dữ liệu cần ghi vào thiết bị
 * @param - cnt 	: Độ dài dữ liệu cần ghi
 * @param - offt 	: Độ lệch so với địa chỉ bắt đầu file
 * @return 			: Số byte ghi được, nếu là giá trị âm, nghĩa là ghi thất bại
 */
static ssize_t device_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int result;
	unsigned char data_buffer[1];
	unsigned char led_state;
	struct led_device *dev = filp->private_data;

	result = copy_from_user(data_buffer, buf, cnt);
	if(result < 0) {
		printk("Ghi kernel thất bại!\r\n");
		return -EFAULT;
	}

	led_state = data_buffer[0];

	if(led_state == LEDON) {	
		gpio_set_value(dev->led_pin, 0);	/* Bật đèn LED */
	} else if(led_state == LEDOFF) {
		gpio_set_value(dev->led_pin, 1);	/* Tắt đèn LED */
	}
	return 0;
}

/*
 * @description		: Đóng/giải phóng thiết bị
 * @param - filp 	: File thiết bị cần đóng (file descriptor)
 * @return 			: 0 Thành công;Khác Thất bại
 */
static int device_release(struct inode *inode, struct file *filp)
{
	unsigned long flags;
	struct led_device *dev = filp->private_data;

	spin_lock_irqsave(&dev->lock, flags);
	if (dev->device_status)
	{
		dev->device_status--;
	}
	spin_unlock_irqrestore(&dev->lock, flags);

	return 0;
}

/* Hàm hoạt động của thiết bị */
static struct file_operations led_device_ops = {
	.owner = THIS_MODULE,
	.open = device_open,
	.read = device_read,
	.write = device_write,
	.release = device_release,
};

/*
 * @description	: Hàm xuất của driver
 * @param 		: Không
 * @return 		: Không
 */
static int __init device_init(void)
{
	int ret = 0;
	const char *str;

	// Khởi tạo spinlock
	spin_lock_init(&led_device.lock);
	
	/* Thiết lập GPIO cho LED */
	/* 1. Lấy node thiết bị: led_device */
	led_device.node = of_find_node_by_path("/led_device");
	if(led_device.node == NULL) {
		printk("Không tìm thấy node led_device!\r\n");
		return -EINVAL;
	}

	/* 2. Đọc thuộc tính status */
	ret = of_property_read_string(led_device.node, "status", &str);
	if(ret < 0) 
	    return -EINVAL;

	if (strcmp(str, "okay"))
        return -EINVAL;
    
	/* 3. Lấy giá trị thuộc tính compatible và khớp */
	ret = of_property_read_string(led_device.node, "compatible", &str);
	if(ret < 0) {
		printk("Không lấy được thuộc tính compatible\r\n");
		return -EINVAL;
	}

    if (strcmp(str, "atf,led")) {
        printk("Khớp compatible thất bại\r\n");
        return -EINVAL;
    }

	/* 4. Lấy thuộc tính GPIO trong cây thiết bị, nhận số LED sử dụng */
	led_device.led_pin = of_get_named_gpio(led_device.node, "led-pin", 0);
	if(led_device.led_pin < 0) {
		printk("Không lấy được led-pin");
		return -EINVAL;
	}
	printk("Số GPIO LED = %d\r\n", led_device.led_pin);

	/* 5. Yêu cầu sử dụng GPIO từ hệ thống GPIO */
	ret = gpio_request(led_device.led_pin, "LED-PIN");
    if (ret) {
        printk(KERN_ERR "Yêu cầu GPIO LED thất bại\r\n");
        return ret;
	}

	/* 6. Thiết lập GPIO là đầu ra, mức cao, mặc định tắt LED */
	ret = gpio_direction_output(led_device.led_pin, 1);
	if(ret < 0) {
		printk("Không thiết lập được GPIO!\r\n");
	}

	/* Đăng ký driver thiết bị ký tự */
	/* 1. Tạo ID thiết bị */
	if (led_device.major_num) {
		led_device.device_id = MKDEV(led_device.major_num, 0);
		ret = register_chrdev_region(led_device.device_id, LEDDEV_CNT, LEDDEV_NAME);
		if(ret < 0) {
			pr_err("Không đăng ký được driver ký tự [ret=%d]\n", LEDDEV_CNT);
			goto free_gpio;
		}
	} else {
		ret = alloc_chrdev_region(&led_device.device_id, 0, LEDDEV_CNT, LEDDEV_NAME);
		if(ret < 0) {
			pr_err("%s Không thể alloc_chrdev_region, ret=%d\r\n", LEDDEV_NAME, ret);
			goto free_gpio;
		}
		led_device.major_num = MAJOR(led_device.device_id);
		led_device.minor_num = MINOR(led_device.device_id);
	}
	printk("Thiết bị chính=%d,phụ=%d\r\n", led_device.major_num, led_device.minor_num);	
	
	/* 2. Khởi tạo cdev */
	led_device.cdev.owner = THIS_MODULE;
	cdev_init(&led_device.cdev, &led_device_ops);
	
	/* 3. Thêm cdev */
	cdev_add(&led_device.cdev, led_device.device_id, LEDDEV_CNT);
	if(ret < 0)
		goto del_unregister;
		
	/* 4. Tạo lớp */
	led_device.device_class = class_create(THIS_MODULE, LEDDEV_NAME);
	if (IS_ERR(led_device.device_class)) {
		goto del_cdev;
	}

	/* 5. Tạo thiết bị */
	led_device.device = device_create(led_device.device_class, NULL, led_device.device_id, NULL, LEDDEV_NAME);
	if (IS_ERR(led_device.device)) {
		goto destroy_class;
	}
	return 0;
	
destroy_class:
	class_destroy(led_device.device_class);
del_cdev:
	cdev_del(&led_device.cdev);
del_unregister:
	unregister_chrdev_region(led_device.device_id, LEDDEV_CNT);
free_gpio:
	gpio_free(led_device.led_pin);
	return -EIO;
}

/*
 * @description	: Hàm thoát của driver
 * @param 		: Không
 * @return 		: Không
 */
static void __exit device_exit(void)
{
	/* Hủy đăng ký driver thiết bị ký tự */
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.device_id, LEDDEV_CNT);
	device_destroy(led_device.device_class, led_device.device_id);
	class_destroy(led_device.device_class);
	gpio_free(led_device.led_pin);
}

module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ATF");
MODULE_INFO(intree, "Y");

Ứng dụng kiểm tra giống như phần trước, chỉ cần đổi tên thành spinlockApp.c.

2.2 Chạy kiểm tra

Sửa Makefile, thay thành spinlock.o.

Biên dịch spinlock.c:

make

Biên dịch spinlockApp.c:

arm-none-linux-gnueabihf-gcc spinlockApp.c -o spinlockApp

Sao chép hai file trên:

sudo cp spinlockApp spinlock.ko /home/atf/linux/nfs/rootfs/lib/modules/5.4.31/

Bắt đầu board, nhập lệnh:

cd lib/modules/5.4.31/
depmod
modprobe spinlock.ko

Sử dụng spinlockApp để kiểm tra driver:

./spinlockApp /dev/leddevice 1&     // Bật đèn LED
./spinlockApp /dev/leddevice 0      // Tắt đèn LED

Nếu driver hoạt động bình thường sẽ không tắt LED ngay, sẽ báo lỗi file /dev/leddevice open failed!, phải đợi spinlockApp đầu tiên chạy xong thì mới có thể tắt.

Gỡ driver:

rmmod spinlock.ko

Phần 3: Thực nghiệm với Semaphore

Sử dụng semaphore để thực hiện chỉ một ứng dụng có thể truy cập LED, vì semaphore có thể gây ra trạng thái ngủ, nên khu vực được bảo vệ bởi semaphore không có giới hạn về thời gian chạy, có thể yêu cầu semaphore trong hàm open và giải phóng trong hàm release.

3.1 Viết chương trình thực nghiệm

Không cần sửa cây thiết bị.

Tạo thư mục 9_semaphore, làm tương tự như phần trước, chỉ cần đổi spinlock thành semaphore. Sửa file semaphore.c:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDDEV_CNT			1		  	/* Số lượng thiết bị */
#define LEDDEV_NAME		"leddevice"	/* Tên thiết bị */
#define LEDOFF 				0			/* Tắt đèn */
#define LEDON 				1			/* Bật đèn */

/* Cấu trúc thiết bị led */
struct led_device{
	dev_t device_id;			/* ID thiết bị */
	struct cdev cdev;		/* cdev */
	struct class *device_class;	/* Lớp */
	struct device *device;	/* Thiết bị */
	int major_num;				/* Số thiết bị chính */
	int minor_num;				/* Số thiết bị phụ */
	struct device_node	*node; /* Node thiết bị */
	int led_pin;			/* Số GPIO cho LED */
	struct semaphore sem;	// Semaphore
};

struct led_device led_device;	/* Thiết bị led */

/*
 * @description		: Mở thiết bị
 * @param - inode 	: inode truyền vào driver
 * @param - filp 	: File thiết bị, cấu trúc file có thành viên private_data
 * 					  Thường trong hàm open, private_data sẽ trỏ đến cấu trúc thiết bị.
 * @return 			: 0 Thành công;Khác Thất bại
 */
static int device_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &led_device;

	if (down_interruptible(&led_device.sem)) {
		return -ERESTARTSYS;
	}

	return 0;
}

/*
 * @description		: Đọc dữ liệu từ thiết bị 
 * @param - filp 	: File thiết bị cần mở (file descriptor)
 * @param - buf 	: Bộ đệm dữ liệu trả về cho không gian người dùng
 * @param - cnt 	: Độ dài dữ liệu cần đọc
 * @param - offt 	: Độ lệch so với địa chỉ bắt đầu file
 * @return 			: Số byte đọc được, nếu là giá trị âm, nghĩa là đọc thất bại
 */
static ssize_t device_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: Ghi dữ liệu vào thiết bị 
 * @param - filp 	: File thiết bị, biểu thị file descriptor được mở
 * @param - buf 	: Dữ liệu cần ghi vào thiết bị
 * @param - cnt 	: Độ dài dữ liệu cần ghi
 * @param - offt 	: Độ lệch so với địa chỉ bắt đầu file
 * @return 			: Số byte ghi được, nếu là giá trị âm, nghĩa là ghi thất bại
 */
static ssize_t device_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int result;
	unsigned char data_buffer[1];
	unsigned char led_state;
	struct led_device *dev = filp->private_data;

	result = copy_from_user(data_buffer, buf, cnt);
	if(result < 0) {
		printk("Ghi kernel thất bại!\r\n");
		return -EFAULT;
	}

	led_state = data_buffer[0];

	if(led_state == LEDON) {	
		gpio_set_value(dev->led_pin, 0);	/* Bật đèn LED */
	} else if(led_state == LEDOFF) {
		gpio_set_value(dev->led_pin, 1);	/* Tắt đèn LED */
	}
	return 0;
}

/*
 * @description		: Đóng/giải phóng thiết bị
 * @param - filp 	: File thiết bị cần đóng (file descriptor)
 * @return 			: 0 Thành công;Khác Thất bại
 */
static int device_release(struct inode *inode, struct file *filp)
{
	struct led_device *dev = filp->private_data;

	up(&dev->sem);
	return 0;
}

/* Hàm hoạt động của thiết bị */
static struct file_operations led_device_ops = {
	.owner = THIS_MODULE,
	.open = device_open,
	.read = device_read,
	.write = device_write,
	.release = device_release,
};

/*
 * @description	: Hàm xuất của driver
 * @param 		: Không
 * @return 		: Không
 */
static int __init device_init(void)
{
	int ret = 0;
	const char *str;

	/* Khởi tạo semaphore */
	sema_init(&led_device.sem, 1);
	
	/* Thiết lập GPIO cho LED */
	/* 1. Lấy node thiết bị: led_device */
	led_device.node = of_find_node_by_path("/led_device");
	if(led_device.node == NULL) {
		printk("Không tìm thấy node led_device!\r\n");
		return -EINVAL;
	}

	/* 2. Đọc thuộc tính status */
	ret = of_property_read_string(led_device.node, "status", &str);
	if(ret < 0) 
	    return -EINVAL;

	if (strcmp(str, "okay"))
        return -EINVAL;
    
	/* 3. Lấy giá trị thuộc tính compatible và khớp */
	ret = of_property_read_string(led_device.node, "compatible", &str);
	if(ret < 0) {
		printk("Không lấy được thuộc tính compatible\r\n");
		return -EINVAL;
	}

    if (strcmp(str, "atf,led")) {
        printk("Khớp compatible thất bại\r\n");
        return -EINVAL;
    }

	/* 4. Lấy thuộc tính GPIO trong cây thiết bị, nhận số LED sử dụng */
	led_device.led_pin = of_get_named_gpio(led_device.node, "led-pin", 0);
	if(led_device.led_pin < 0) {
		printk("Không lấy được led-pin");
		return -EINVAL;
	}
	printk("Số GPIO LED = %d\r\n", led_device.led_pin);

	/* 5. Yêu cầu sử dụng GPIO từ hệ thống GPIO */
	ret = gpio_request(led_device.led_pin, "LED-PIN");
    if (ret) {
        printk(KERN_ERR "Yêu cầu GPIO LED thất bại\r\n");
        return ret;
	}

	/* 6. Thiết lập GPIO là đầu ra, mức cao, mặc định tắt LED */
	ret = gpio_direction_output(led_device.led_pin, 1);
	if(ret < 0) {
		printk("Không thiết lập được GPIO!\r\n");
	}

	/* Đăng ký driver thiết bị ký tự */
	/* 1. Tạo ID thiết bị */
	if (led_device.major_num) {
		led_device.device_id = MKDEV(led_device.major_num, 0);
		ret = register_chrdev_region(led_device.device_id, LEDDEV_CNT, LEDDEV_NAME);
		if(ret < 0) {
			pr_err("Không đăng ký được driver ký tự [ret=%d]\n", LEDDEV_CNT);
			goto free_gpio;
		}
	} else {
		ret = alloc_chrdev_region(&led_device.device_id, 0, LEDDEV_CNT, LEDDEV_NAME);
		if(ret < 0) {
			pr_err("%s Không thể alloc_chrdev_region, ret=%d\r\n", LEDDEV_NAME, ret);
			goto free_gpio;
		}
		led_device.major_num = MAJOR(led_device.device_id);
		led_device.minor_num = MINOR(led_device.device_id);
	}
	printk("Thiết bị chính=%d,phụ=%d\r\n", led_device.major_num, led_device.minor_num);	
	
	/* 2. Khởi tạo cdev */
	led_device.cdev.owner = THIS_MODULE;
	cdev_init(&led_device.cdev, &led_device_ops);
	
	/* 3. Thêm cdev */
	cdev_add(&led_device.cdev, led_device.device_id, LEDDEV_CNT);
	if(ret < 0)
		goto del_unregister;
		
	/* 4. Tạo lớp */
	led_device.device_class = class_create(THIS_MODULE, LEDDEV_NAME);
	if (IS_ERR(led_device.device_class)) {
		goto del_cdev;
	}

	/* 5. Tạo thiết bị */
	led_device.device = device_create(led_device.device_class, NULL, led_device.device_id, NULL, LEDDEV_NAME);
	if (IS_ERR(led_device.device)) {
		goto destroy_class;
	}
	return 0;
	
destroy_class:
	class_destroy(led_device.device_class);
del_cdev:
	cdev_del(&led_device.cdev);
del_unregister:
	unregister_chrdev_region(led_device.device_id, LEDDEV_CNT);
free_gpio:
	gpio_free(led_device.led_pin);
	return -EIO;
}

/*
 * @description	: Hàm thoát của driver
 * @param 		: Không
 * @return 		: Không
 */
static void __exit device_exit(void)
{
	/* Hủy đăng ký driver thiết bị ký tự */
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.device_id, LEDDEV_CNT);
	device_destroy(led_device.device_class, led_device.device_id);
	class_destroy(led_device.device_class);
	gpio_free(led_device.led_pin);
}

module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ATF");
MODULE_INFO(intree, "Y");

Khi semaphore sem bằng 1, nghĩa là LED chưa được sử dụng. Nếu ứng dụng A muốn sử dụng LED, trước tiên gọi hàm open để mở /dev/leddevice, lúc này sẽ lấy semaphore sem, lấy thành công thì giá trị của sem giảm 1 thành 0. Nếu lúc này ứng dụng B cũng muốn sử dụng LED, gọi hàm open để mở /dev/leddevice sẽ do semaphore không hiệu lực (giá trị 0) mà vào trạng thái ngủ. Khi ứng dụng A chạy xong, gọi hàm close để đóng /dev/leddevice sẽ giải phóng semaphore sem, lúc này giá trị của sem tăng 1 thành 1. Semaphore sem lại hiệu lực, nghĩa là ứng dụng khác có thể sử dụng LED, ứng dụng B đang ở trạng thái ngủ sẽ lấy được semaphore sem, lấy thành công thì bắt đầu sử dụng LED.

3.2 Chạy kiểm tra

Sửa file Makefile, giống như phần trước, chỉ cần thay thành obj-m := semaphore.o.

Biên dịch semaphore.c:

make

Biên dịch semaphoreApp.c:

arm-none-linux-gnueabihf-gcc semaphoreApp.c -o semaphoreApp

Sao chép hai file trên:

sudo cp semaphoreApp semaphore.ko /home/atf/linux/nfs/rootfs/lib/modules/5.4.31/

Bắt đầu board, tải driver:

depmod
modprobe semaphore.ko

Driver tải xong, sử dụng semaphoreApp để kiểm tra driver:

./semaphoreApp /dev/leddevice 1&  # Bật đèn LED
./semaphoreApp /dev/leddevice 0&  # Tắt đèn LED

Đầu tiên, lệnh đầu tiên lấy semaphore, do đó có thể điều khiển LED, lúc này LED trên board sáng. Lệnh thứ hai cũng muốn có quyền sử dụng LED, nhưng bị lệnh đầu tiên chiếm trước, nên lệnh thứ hai vào trạng thái ngủ, đợi lệnh đầu tiên hoàn thành thì giải phóng semaphore, lệnh thứ hai mới có quyền sử dụng LED, lúc này thấy LED trên board tắt. Tổng cộng, LED trên board sáng trong 25 giây đầu, tắt trong 25 giây sau.

Gỡ driver:

rmmod semaphore.ko

Phần 4: Thực nghiệm với Mutex

Thực tế, mutex là lựa chọn phù hợp nhất cho việc tương hỗ mutex.

4.1 Viết chương trình thực nghiệm

Không cần sửa cây thiết bị.

Làm tương tự như phần trước, tất cả đều đổi thành mutex. Đừng quên thêm đường dẫn header mỗi lần, sửa file mutex.c:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDDEV_CNT			1		  	/* Số lượng thiết bị */
#define LEDDEV_NAME		"leddevice"	/* Tên thiết bị */
#define LEDOFF 				0			/* Tắt đèn */
#define LEDON 				1			/* Bật đèn */

/* Cấu trúc thiết bị led */
struct led_device{
	dev_t device_id;			/* ID thiết bị */
	struct cdev cdev;		/* cdev */
	struct class *device_class;	/* Lớp */
	struct device *device;	/* Thiết bị */
	int major_num;				/* Số thiết bị chính */
	int minor_num;				/* Số thiết bị phụ */
	struct device_node	*node; /* Node thiết bị */
	int led_pin;			/* Số GPIO cho LED */
	struct mutex lock;		// Định nghĩa mutex
};

struct led_device led_device;	/* Thiết bị led */

/*
 * @description		: Mở thiết bị
 * @param - inode 	: inode truyền vào driver
 * @param - filp 	: File thiết bị, cấu trúc file có thành viên private_data
 * 					  Thường trong hàm open, private_data sẽ trỏ đến cấu trúc thiết bị.
 * @return 			: 0 Thành công;Khác Thất bại
 */
static int device_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &led_device;

	if (mutex_lock_interruptible(&led_device.lock)) {
		return -ERESTARTSYS;
	}

	return 0;
}

/*
 * @description		: Đọc dữ liệu từ thiết bị 
 * @param - filp 	: File thiết bị cần mở (file descriptor)
 * @param - buf 	: Bộ đệm dữ liệu trả về cho không gian người dùng
 * @param - cnt 	: Độ dài dữ liệu cần đọc
 * @param - offt 	: Độ lệch so với địa chỉ bắt đầu file
 * @return 			: Số byte đọc được, nếu là giá trị âm, nghĩa là đọc thất bại
 */
static ssize_t device_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: Ghi dữ liệu vào thiết bị 
 * @param - filp 	: File thiết bị, biểu thị file descriptor được mở
 * @param - buf 	: Dữ liệu cần ghi vào thiết bị
 * @param - cnt 	: Độ dài dữ liệu cần ghi
 * @param - offt 	: Độ lệch so với địa chỉ bắt đầu file
 * @return 			: Số byte ghi được, nếu là giá trị âm, nghĩa là ghi thất bại
 */
static ssize_t device_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int result;
	unsigned char data_buffer[1];
	unsigned char led_state;
	struct led_device *dev = filp->private_data;

	result = copy_from_user(data_buffer, buf, cnt);
	if(result < 0) {
		printk("Ghi kernel thất bại!\r\n");
		return -EFAULT;
	}

	led_state = data_buffer[0];

	if(led_state == LEDON) {	
		gpio_set_value(dev->led_pin, 0);	/* Bật đèn LED */
	} else if(led_state == LEDOFF) {
		gpio_set_value(dev->led_pin, 1);	/* Tắt đèn LED */
	}
	return 0;
}

/*
 * @description		: Đóng/giải phóng thiết bị
 * @param - filp 	: File thiết bị cần đóng (file descriptor)
 * @return 			: 0 Thành công;Khác Thất bại
 */
static int device_release(struct inode *inode, struct file *filp)
{
	struct led_device *dev = filp->private_data;

	mutex_unlock(&dev->lock);
	return 0;
}

/* Hàm hoạt động của thiết bị */
static struct file_operations led_device_ops = {
	.owner = THIS_MODULE,
	.open = device_open,
	.read = device_read,
	.write = device_write,
	.release = device_release,
};

/*
 * @description	: Hàm xuất của driver
 * @param 		: Không
 * @return 		: Không
 */
static int __init device_init(void)
{
	int ret = 0;
	const char *str;

	/* Khởi tạo mutex */
	mutex_init(&led_device.lock);
	
	/* Thiết lập GPIO cho LED */
	/* 1. Lấy node thiết bị: led_device */
	led_device.node = of_find_node_by_path("/led_device");
	if(led_device.node == NULL) {
		printk("Không tìm thấy node led_device!\r\n");
		return -EINVAL;
	}

	/* 2. Đọc thuộc tính status */
	ret = of_property_read_string(led_device.node, "status", &str);
	if(ret < 0) 
	    return -EINVAL;

	if (strcmp(str, "okay"))
        return -EINVAL;
    
	/* 3. Lấy giá trị thuộc tính compatible và khớp */
	ret = of_property_read_string(led_device.node, "compatible", &str);
	if(ret < 0) {
		printk("Không lấy được thuộc tính compatible\r\n");
		return -EINVAL;
	}

    if (strcmp(str, "atf,led")) {
        printk("Khớp compatible thất bại\r\n");
        return -EINVAL;
    }

	/* 4. Lấy thuộc tính GPIO trong cây thiết bị, nhận số LED sử dụng */
	led_device.led_pin = of_get_named_gpio(led_device.node, "led-pin", 0);
	if(led_device.led_pin < 0) {
		printk("Không lấy được led-pin");
		return -EINVAL;
	}
	printk("Số GPIO LED = %d\r\n", led_device.led_pin);

	/* 5. Yêu cầu sử dụng GPIO từ hệ thống GPIO */
	ret = gpio_request(led_device.led_pin, "LED-PIN");
    if (ret) {
        printk(KERN_ERR "Yêu cầu GPIO LED thất bại\r\n");
        return ret;
	}

	/* 6. Thiết lập GPIO là đầu ra, mức cao, mặc định tắt LED */
	ret = gpio_direction_output(led_device.led_pin, 1);
	if(ret < 0) {
		printk("Không thiết lập được GPIO!\r\n");
	}

	/* Đăng ký driver thiết bị ký tự */
	/* 1. Tạo ID thiết bị */
	if (led_device.major_num) {
		led_device.device_id = MKDEV(led_device.major_num, 0);
		ret = register_chrdev_region(led_device.device_id, LEDDEV_CNT, LEDDEV_NAME);
		if(ret < 0) {
			pr_err("Không đăng ký được driver ký tự [ret=%d]\n", LEDDEV_CNT);
			goto free_gpio;
		}
	} else {
		ret = alloc_chrdev_region(&led_device.device_id, 0, LEDDEV_CNT, LEDDEV_NAME);
		if(ret < 0) {
			pr_err("%s Không thể alloc_chrdev_region, ret=%d\r\n", LEDDEV_NAME, ret);
			goto free_gpio;
		}
		led_device.major_num = MAJOR(led_device.device_id);
		led_device.minor_num = MINOR(led_device.device_id);
	}
	printk("Thiết bị chính=%d,phụ=%d\r\n", led_device.major_num, led_device.minor_num);	
	
	/* 2. Khởi tạo cdev */
	led_device.cdev.owner = THIS_MODULE;
	cdev_init(&led_device.cdev, &led_device_ops);
	
	/* 3. Thêm cdev */
	cdev_add(&led_device.cdev, led_device.device_id, LEDDEV_CNT);
	if(ret < 0)
		goto del_unregister;
		
	/* 4. Tạo lớp */
	led_device.device_class = class_create(THIS_MODULE, LEDDEV_NAME);
	if (IS_ERR(led_device.device_class)) {
		goto del_cdev;
	}

	/* 5. Tạo thiết bị */
	led_device.device = device_create(led_device.device_class, NULL, led_device.device_id, NULL, LEDDEV_NAME);
	if (IS_ERR(led_device.device)) {
		goto destroy_class;
	}
	return 0;
	
destroy_class:
	class_destroy(led_device.device_class);
del_cdev:
	cdev_del(&led_device.cdev);
del_unregister:
	unregister_chrdev_region(led_device.device_id, LEDDEV_CNT);
free_gpio:
	gpio_free(led_device.led_pin);
	return -EIO;
}

/*
 * @description	: Hàm thoát của driver
 * @param 		: Không
 * @return 		: Không
 */
static void __exit device_exit(void)
{
	/* Hủy đăng ký driver thiết bị ký tự */
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.device_id, LEDDEV_CNT);
	device_destroy(led_device.device_class, led_device.device_id);
	class_destroy(led_device.device_class);
	gpio_free(led_device.led_pin);
}

module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ATF");
MODULE_INFO(intree, "Y");

4.2 Chạy kiểm tra

Sửa Makefile, mutex.o.

Biên dịch mutex.c và mutexApp.c:

make
arm-none-linux-gnueabihf-gcc mutexApp.c -o mutexApp

Sao chép hai file trên vào:

sudo cp mutexApp mutex.ko /home/atf/linux/nfs/rootfs/lib/modules/5.4.31/

Tải driver:

depmod
modprobe mutex.ko

Sử dụng mutexApp để kiểm tra driver:

./mutexApp /dev/leddevice 1&  # Bật đèn LED
./mutexApp /dev/leddevice 0&  # Tắt đèn LED

Hiệu quả giống như semaphore. Gỡ driver:

rmmod mutex.ko

Thẻ: linux device driver Synchronization atomic operations spinlock

Đăng vào ngày 16 tháng 6 lúc 19:56