Giới thiệu về virt_to_page
Trong kernel Linux, virt_to_page là một macro quan trọng dùng để chuyển đổi địa chỉ ảo thành con trỏ đến cấu trúc struct page tương ứng. Mỗi trang vật lý trong bộ nhớ được quản lý bởi một cấu trúc struct page chứa thông tin chi tiết về trạng thái, bộ đếm tham chiếu và thông tin ánh xạ của trang.
Kernel Linux phân chia không gian địa chỉ ảo thành nhiều vùng khác nhau, bao gồm không gian kernel và không gian người dùng. Mỗi vùng được chia thành các trang có kích thước thường là 4KB. Kernel sử dụng bảng trang để quản lý ánh xạ từ địa chỉ ảo đến địa chỉ vật lý. Macro virt_to_page tận dụng các thông tin này để tìm kiếm cấu trúc page tương ứng với địa chỉ ảo đã cho.
Khi virt_to_page được gọi, nó sẽ kiểm tra tính hợp lệ của địa chỉ ảo, xác định xem địa chỉ đó có thuộc không gian kernel hay không. Nếu địa chỉ hợp lệ, macro sẽ xác định mục bảng trang chứa địa chỉ đó và trích xuất số trang vật lý (PFN). PFN là định danh duy nhất của trang vật lý. Sau khi xác định được PFN, virt_to_page sẽ sử dụng nó để tìm cấu trúc page tương ứng.
Cấu trúc page là cấu trúc dữ liệu chính mà kernel sử dụng để đại diện cho trang vật lý. Nó chứa các thuộc tính khác nhau của trang như trạng thái (đã được cấp phát, có bị bẩn hay không), quyền truy cập trang, thông tin ánh xạ, v.v. Thông qua cấu trúc page, kernel có thể thực hiện các thao tác quản lý bộ nhớ khác nhau như cấp phát trang, giải phóng trang, đánh dấu trang bẩn để ghi lại, v.v.
virt_to_page được sử dụng rộng rãi trong kernel, đặc biệt là trong các module quản lý bộ nhớ, bộ đệm trang, hệ thống tệp, v.v. Ví dụ, khi kernel cần truy cập một địa chỉ ảo kernel cụ thể, nó có thể sử dụng virt_to_page để lấy cấu trúc page tương ứng, sau đó kiểm tra trạng thái trang hoặc thực hiện các thao tác khác.
Tuy nhiên, cần lưu ý rằng virt_to_page thường chỉ áp dụng cho địa chỉ ảo của kernel. Đối với địa chỉ ảo của không gian người dùng, kernel sử dụng các hàm khác nhau để lấy cấu trúc page tương ứng, vì địa chỉ ảo của không gian người dùng có thể liên quan đến các cơ chế quản lý bộ nhớ phức tạp hơn như chia sẻ bảng trang, copy-on-write, v.v.
Tóm lại, virt_to_page là một macro quan trọng trong kernel Linux, đơn giản hóa quá trình chuyển đổi từ địa chỉ ảo sang trang vật lý, giúp kernel quản lý tài nguyên bộ nhớ một cách hiệu quả.
Cách hoạt động của virt_to_page
Macro virt_to_page được triển khai thông qua một chuỗi các macro và hàm khác. Dưới đây là quá trình mở rộng của macro này:
Đầu tiên, virt_to_page được định nghĩa như sau:
#define virt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
Tiếp theo, pfn_to_page được mở rộng thành:
#define pfn_to_page __pfn_to_page
#define __pfn_to_page(pfn) \
({ unsigned long __pfn = (pfn); \
struct mem_section *__sec = __pfn_to_section(__pfn); \
__section_mem_map_addr(__sec) + __pfn; \
})
Cuối cùng, __pfn_to_section được triển khai như sau:
static inline struct mem_section *__pfn_to_section(unsigned long pfn)
{
return __nr_to_section(pfn_to_section_nr(pfn));
}
Quá trình này bắt đầu bằng cách chuyển đổi địa chỉ ảo thành địa chỉ vật lý bằng macro __pa, sau đó dịch phải theo PAGE_SHIFT để lấy PFN. PFN sau đó được sử dụng để tìm section bộ nhớ tương ứng và cuối cùng lấy được con trỏ đến cấu trúc page.
Ví dụ sử dụng virt_to_page trong driver
Driver thiết bị ký tự
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#define CHAR_DEV_NAME "virt_page_demo"
#define DEMO_BUFFER_SIZE 2048
static char demo_buffer[DEMO_BUFFER_SIZE];
static unsigned long kernel_virt_addr = (unsigned long)demo_buffer;
static int char_dev_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Character device opened\n");
return 0;
}
static ssize_t char_dev_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
struct page *target_page;
void *page_data;
target_page = virt_to_page((void *)kernel_virt_addr);
page_data = page_address(target_page);
if (copy_to_user(buf, page_data, count))
return -EFAULT;
return count;
}
static const struct file_operations char_fops = {
.owner = THIS_MODULE,
.open = char_dev_open,
.read = char_dev_read,
};
static int __init char_driver_init(void)
{
major = register_chrdev(0, CHAR_DEV_NAME, &char_fops);
if (major < 0) {
printk(KERN_ERR "Failed to register character device\n");
return major;
}
printk(KERN_INFO "Registered character device with major %d\n", major);
return 0;
}
static void __exit char_driver_exit(void)
{
unregister_chrdev(major, CHAR_DEV_NAME);
printk(KERN_INFO "Character driver unloaded\n");
}
module_init(char_driver_init);
module_exit(char_driver_exit);
MODULE_LICENSE("GPL");
Driver thiết bị nền tảng
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/mm.h>
#define PLATFORM_DRV_NAME "page_platform_demo"
struct platform_data {
void *mapped_addr;
struct page *page_ptr;
};
static int platform_probe(struct platform_device *pdev)
{
struct platform_data *pdata;
struct resource *res;
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
pdata->mapped_addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(pdata->mapped_addr))
return PTR_ERR(pdata->mapped_addr);
pdata->page_ptr = virt_to_page(pdata->mapped_addr);
platform_set_drvdata(pdev, pdata);
dev_info(&pdev->dev, "Platform device probed successfully\n");
return 0;
}
static int platform_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "Platform device removed\n");
return 0;
}
static struct platform_driver platform_drv = {
.probe = platform_probe,
.remove = platform_remove,
.driver = {
.name = PLATFORM_DRV_NAME,
},
};
module_platform_driver(platform_drv);
MODULE_LICENSE("GPL");
Driver thiết bị I2C
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/mm.h>
#define I2C_DRV_NAME "page_i2c_demo"
#define TRANSFER_BUFFER_SIZE 1024
static struct i2c_client *i2c_client;
static char transfer_buffer[TRANSFER_BUFFER_SIZE];
static int i2c_transfer_data(struct i2c_client *client,
const char *data, int len)
{
struct page *buf_page;
void *page_addr;
buf_page = virt_to_page(transfer_buffer);
page_addr = page_address(buf_page);
memcpy(page_addr, data, len);
return i2c_master_send(client, page_addr, len);
}
static int i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
i2c_client = client;
dev_info(&client->dev, "I2C device probed\n");
return 0;
}
static int i2c_remove(struct i2c_client *client)
{
dev_info(&client->dev, "I2C device removed\n");
return 0;
}
static const struct i2c_device_id i2c_id[] = {
{ I2C_DRV_NAME, 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, i2c_id);
static struct i2c_driver i2c_drv = {
.driver = {
.name = I2C_DRV_NAME,
},
.probe = i2c_probe,
.remove = i2c_remove,
.id_table = i2c_id,
};
module_i2c_driver(i2c_drv);
MODULE_LICENSE("GPL");