内核模块程序
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define MY_BUFFER_SIZE (4 * PAGE_SIZE)
static char *my_buffer;
struct vm_area_struct *vma;
static int my_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
struct page *page = NULL;
unsigned long addr = (((unsigned long)vmf->virtual_address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT));
printk(KERN_ERR "my_fault enter\n");
if (addr >= MY_BUFFER_SIZE) {
return VM_FAULT_SIGBUS;
}
if (!my_buffer) {
my_buffer = kmalloc(MY_BUFFER_SIZE, GFP_KERNEL);
if (!my_buffer) {
return VM_FAULT_OOM;
}
}
page = virt_to_page(my_buffer + addr);
get_page(page);
vmf->page = page;
printk(KERN_ERR "my_fault ok\n");
return 0;
}
static struct vm_operations_struct my_vm_ops = {
.fault = my_fault,
};
static int my_mmap(struct file *filp, struct vm_area_struct *vmap)
{
/* int ret;
unsigned long pfn = virt_to_phys((void *)my_buffer) >> PAGE_SHIFT;
ret = remap_pfn_range(vma, vma->vm_start, pfn, vma->vm_end - vma->vm_start, vma->vm_page_prot);
if (ret < 0) {
printk(KERN_ERR "Failed to remap buffer\n");
return ret;
}
*/
vmap->vm_ops = &my_vm_ops;
vma = vmap;
printk(KERN_ERR "my_mmap ok\n");
return 0;
}
static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
int len;
if (*offset >= MY_BUFFER_SIZE) {
return 0;
}
len = min(count, (size_t)(MY_BUFFER_SIZE - *offset));
if (copy_to_user(buf, my_buffer + *offset, len)) {
return -EFAULT;
}
*offset += len;
return len;
}
static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
int len;
if (*offset >= MY_BUFFER_SIZE) {
return 0;
}
len = min(count, (size_t)(MY_BUFFER_SIZE - *offset));
if (copy_from_user(my_buffer + *offset, buf, len)) {
return -EFAULT;
}
*offset += len;
return len;
}
static int my_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int my_release(struct inode *inode, struct file *filp)
{
if(my_buffer)
{
kfree(my_buffer);
my_buffer = NULL;
printk(KERN_ERR "my_release ok\n");
}
return 0;
}
static loff_t my_llseek(struct file *file, loff_t offset, int whence)
{
loff_t newpos;
switch (whence) {
case 0: /* SEEK_SET */
newpos = offset;
break;
case 1: /* SEEK_CUR */
newpos = file->f_pos + offset;
break;
case 2: /* SEEK_END */
newpos = MY_BUFFER_SIZE + offset;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0 || newpos > MY_BUFFER_SIZE) {
return -EINVAL;
}
file->f_pos = newpos;
return newpos;
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.read = my_read,
.write = my_write,
.llseek = my_llseek,
.open = my_open,
.release = my_release,
.mmap = my_mmap,
};
static int __init my_init(void)
{
int ret;
ret = register_chrdev(0, "my_module2", &my_fops);
if (ret < 0) {
printk(KERN_ERR "Failed to register device\n");
return ret;
}
return 0;
}
static void __exit my_exit(void)
{
unregister_chrdev(0, "my_module2");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
应用层程序
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define BUF_SIZE (4 * 4096)
int main(int argc, char **argv)
{
int fd, ret;
char *buf;
off_t offset = 0;
fd = open("/dev/my_module", O_RDWR);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
buf = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
printf("Writing data to buffer...\n");
snprintf(buf, BUF_SIZE, "Hello, world!");
printf("Reading data from buffer...\n");
ret = read(fd, buf, BUF_SIZE);
if (ret < 0) {
perror("read");
exit(EXIT_FAILURE);
}
printf("Data read from buffer: %s\n", buf);
printf("Seeking to offset 6...\n");
offset = lseek(fd, 6, SEEK_SET);
if (offset < 0) {
perror("lseek");
exit(EXIT_FAILURE);
}
printf("Reading data from buffer at offset 6...\n");
ret = read(fd, buf, BUF_SIZE);
if (ret < 0) {
perror("read");
exit(EXIT_FAILURE);
}
printf("Data read from buffer at offset 6: %s\n", buf);
munmap(buf, BUF_SIZE);
close(fd);
return 0;
}
执行输出,触发了四次缺页异常
# ./a_delay.out
my_mmap ok
Writing data to buffer...
my_fault enter
my_fault ok
Reading data from buffer...
my_fault enter
my_fault ok
my_fault enter
my_fault ok
my_fault enter
my_fault ok
Data read from buffer: Hello, world!
Seeking to offset 6...
Reading data from buffer at offset 6...
Data read from buffer at offset 6: world!
my_release ok
# dmesg
[ 1370.921056] my_mmap ok
[ 1370.924994] my_fault enter
[ 1370.925145] my_fault ok
[ 1370.925995] my_fault enter
[ 1370.926114] my_fault ok
[ 1370.926203] my_fault enter
[ 1370.926263] my_fault ok
[ 1370.926355] my_fault enter
[ 1370.926414] my_fault ok
[ 1370.945439] my_release ok
补充说明:
filemap_fault函数是Linux内核中的一种函数,用于在用户空间进程访问文件时,将文件映射到内存中。该函数的作用是将文件页映射到用户空间的虚拟地址空间中,以便进程可以直接访问文件的内容。该函数的实现过程中,会先检查文件页是否已经在内存中,如果不在则会调用文件系统的readpage函数将文件页读入内存。如果文件页已经在内存中,则直接将该页映射到用户进程的虚拟地址空间中。