File System and Device
内存映射文件
除了 read()
和 write()
来读写文件外,还可以利用 mmap()
将文件字节序列映射到进程虚拟地址空间,随后进程可以使用内存访问指令来直接访问文件内容。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: ./a.out [offset]\n");
return 1;
}
// open the file
int fd = open("mmapdemo.c", O_RDWR);
if (fd == -1) {
perror("open failed");
return 1;
}
// get file attributes
struct stat sbuf;
if (stat("mmapdemo.c", &sbuf) == -1) {
perror("stat failed");
return 1;
}
// get offset
int offset = atoi(argv[1]);
if (offset < 0 || offset > sbuf.st_size - 1) {
fprintf(stderr, "offset must be in the range 0-%ld\n", sbuf.st_size - 1);
return 1;
}
// memory map
char *data = mmap(NULL, sbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == (void*)(-1)) {
perror("mmap failed");
return 1;
}
printf("byte at offset %d is '%c'\n", offset, data[offset]);
data[offset] = '!';
munmap(data, sbuf.st_size);
return 0;
}
设备驱动程序
设备驱动程序为底层硬件的访问提供了抽象的接口 (例如,发送数据和接收数据)。通过实现这些接口,我们就可以创建一个可供系统访问的 “设备”。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "hello"
static int dev_major = 0;
static struct class *dev_class = NULL;
static struct cdev dev_cdev;
static ssize_t dev_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t dev_write(struct file *, const char __user *, size_t, loff_t *);
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = dev_read,
.write = dev_write,
};
static int __init dev_init(void) {
dev_t dev;
alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME); // allocate device range
dev_major = MAJOR(dev); // create device major number
// create class
dev_class = class_create(DEVICE_NAME);
// init and register
cdev_init(&dev_cdev, &fops);
cdev_add(&dev_cdev, dev, 1);
device_create(dev_class, NULL, dev, NULL, DEVICE_NAME);
return 0;
}
static void __exit dev_exit(void) {
dev_t dev = MKDEV(dev_major, 0);
device_destroy(dev_class, dev);
cdev_del(&dev_cdev);
class_unregister(dev_class);
class_destroy(dev_class);
unregister_chrdev_region(dev, 1);
}
static ssize_t dev_read(struct file *file, char __user *buf, size_t count, loff_t *offset) {
if (*offset != 0) {
return 0;
} else {
uint8_t *data = "Hello World!\n";
size_t datalen = strlen(data);
if (count > datalen) {
count = datalen;
}
if (copy_to_user(buf, data, count)) {
return -EFAULT;
}
*offset += count;
return count;
}
}
static ssize_t dev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) {
printk(KERN_INFO "Received data and ingore (count = %d) \n", (int)count);
return count;
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hello");
Makefile:
# Set the name of the kernel module
obj-m := hello.o
# Set the path to the kernel source directory
KERNEL_SRC := /lib/modules/$(shell uname -r)/build
# Set the current working directory
PWD := $(shell pwd)
# Default target to build the kernel module
all:
$(MAKE) -C $(KERNEL_SRC) M=$(PWD) modules
gcc -static $(CFLAGS) -o write user.c
# Target to clean the build artifacts
clean:
$(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean
rm write
.PHONY: all clean
在编译后,可以通过 sudo insmod hello.ko
动态加载内核模块,然后就可以对新注册的 /dev/hello
设备进行 read()
或 write()
。