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[]) {
int offset = 9;
// ignore error handling
// open the file, and get file attributes
int fd = open("demo.txt", O_RDWR);
struct stat st;
stat("demo.txt", &st);
printf("fd = %d, size = %ld\n", fd, st.st_size);
// 1) read and write
char byte;
lseek(fd, offset, SEEK_SET);
read(fd, &byte, 1);
printf("byte at offset %d is '%c'\n", offset, byte);
write(fd, &byte, 1);
// 2) memory map
char *data = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == (void*)(-1)) {
perror("mmap failed");
return 1;
}
data[offset] = '!';
printf("byte at offset %d is '%c'\n", offset, data[offset]);
munmap(data, st.st_size);
close(fd);
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 char *my_devnode(const struct device *dev, umode_t *mode) {
if (mode) {
*mode = 0666; // permission
}
return NULL;
}
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);
dev_class->devnode = my_devnode;
// 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()。