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()