completed, not tested
This commit is contained in:
61
.vscode/settings.json
vendored
61
.vscode/settings.json
vendored
@@ -1,61 +0,0 @@
|
|||||||
{
|
|
||||||
"files.associations": {
|
|
||||||
"array": "cpp",
|
|
||||||
"atomic": "cpp",
|
|
||||||
"bit": "cpp",
|
|
||||||
"*.tcc": "cpp",
|
|
||||||
"cctype": "cpp",
|
|
||||||
"chrono": "cpp",
|
|
||||||
"clocale": "cpp",
|
|
||||||
"cmath": "cpp",
|
|
||||||
"compare": "cpp",
|
|
||||||
"concepts": "cpp",
|
|
||||||
"condition_variable": "cpp",
|
|
||||||
"cstdarg": "cpp",
|
|
||||||
"cstddef": "cpp",
|
|
||||||
"cstdint": "cpp",
|
|
||||||
"cstdio": "cpp",
|
|
||||||
"cstdlib": "cpp",
|
|
||||||
"ctime": "cpp",
|
|
||||||
"cwchar": "cpp",
|
|
||||||
"cwctype": "cpp",
|
|
||||||
"deque": "cpp",
|
|
||||||
"string": "cpp",
|
|
||||||
"unordered_map": "cpp",
|
|
||||||
"vector": "cpp",
|
|
||||||
"exception": "cpp",
|
|
||||||
"algorithm": "cpp",
|
|
||||||
"functional": "cpp",
|
|
||||||
"iterator": "cpp",
|
|
||||||
"memory": "cpp",
|
|
||||||
"memory_resource": "cpp",
|
|
||||||
"numeric": "cpp",
|
|
||||||
"random": "cpp",
|
|
||||||
"ratio": "cpp",
|
|
||||||
"string_view": "cpp",
|
|
||||||
"system_error": "cpp",
|
|
||||||
"tuple": "cpp",
|
|
||||||
"type_traits": "cpp",
|
|
||||||
"utility": "cpp",
|
|
||||||
"initializer_list": "cpp",
|
|
||||||
"iosfwd": "cpp",
|
|
||||||
"iostream": "cpp",
|
|
||||||
"istream": "cpp",
|
|
||||||
"limits": "cpp",
|
|
||||||
"mutex": "cpp",
|
|
||||||
"new": "cpp",
|
|
||||||
"numbers": "cpp",
|
|
||||||
"ostream": "cpp",
|
|
||||||
"semaphore": "cpp",
|
|
||||||
"stdexcept": "cpp",
|
|
||||||
"stop_token": "cpp",
|
|
||||||
"streambuf": "cpp",
|
|
||||||
"thread": "cpp",
|
|
||||||
"typeinfo": "cpp",
|
|
||||||
"syncstream": "cpp",
|
|
||||||
"csignal": "cpp",
|
|
||||||
"sstream": "cpp",
|
|
||||||
"fstream": "cpp"
|
|
||||||
},
|
|
||||||
"C_Cpp.default.compilerPath": "/usr/bin/clang++"
|
|
||||||
}
|
|
||||||
24
lab6/.vscode/c_cpp_properties.json
vendored
Normal file
24
lab6/.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux Kernel Module",
|
||||||
|
"includePath": [
|
||||||
|
"/usr/lib/modules/6.14.3-arch1-1/build/include",
|
||||||
|
"/usr/lib/modules/6.14.3-arch1-1/build/arch/x86/include",
|
||||||
|
"/usr/lib/modules/6.14.3-arch1-1/build/include/uapi",
|
||||||
|
"/usr/lib/modules/6.14.3-arch1-1/build/include/generated/uapi",
|
||||||
|
"/usr/lib/modules/6.14.3-arch1-1/build/include/generated",
|
||||||
|
"/usr/lib/modules/6.14.3-arch1-1/build/arch/x86/include/generated"
|
||||||
|
],
|
||||||
|
"defines": [
|
||||||
|
"__KERNEL__",
|
||||||
|
"MODULE"
|
||||||
|
],
|
||||||
|
"compilerPath": "/usr/bin/g++",
|
||||||
|
"cStandard": "c11",
|
||||||
|
"cppStandard": "c++20",
|
||||||
|
"intelliSenseMode": "linux-gcc-x64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
9
lab6/.vscode/settings.json
vendored
Normal file
9
lab6/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"cdev.h": "c",
|
||||||
|
"module.h": "c",
|
||||||
|
"types.h": "c",
|
||||||
|
"init.h": "c"
|
||||||
|
},
|
||||||
|
"C_Cpp.default.compilerPath": "/usr/bin/clang++"
|
||||||
|
}
|
||||||
13
lab6/Makefile
Normal file
13
lab6/Makefile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
obj-m += mypipe.o
|
||||||
|
OUT_DIR := build
|
||||||
|
|
||||||
|
.PHONY kern_mod writer reader clean
|
||||||
|
all: kern_mod writer reader
|
||||||
|
|
||||||
|
kern_mod: mypipe.c
|
||||||
|
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
|
||||||
|
|
||||||
|
clean:
|
||||||
|
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
|
||||||
|
rm -rf $(OUT_DIR)
|
||||||
|
|
||||||
230
lab6/mypipe.c
Normal file
230
lab6/mypipe.c
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#include <linux/cdev.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
|
||||||
|
// Ref:
|
||||||
|
// https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html
|
||||||
|
// https://olegkutkov.me/2018/03/14/simple-linux-character-device-driver/
|
||||||
|
|
||||||
|
#define MYPIPE_BUFFER_SIZE 4096
|
||||||
|
#define MYPIPE_DEVICE_NAME "wendy"
|
||||||
|
|
||||||
|
static dev_t dev_major = 0;
|
||||||
|
|
||||||
|
enum MyPipeMinors {
|
||||||
|
MYPIPE_MINOR_IN = 0,
|
||||||
|
MYPIPE_MINOR_OUT,
|
||||||
|
MYPIPE_MINOR_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mypipe_data {
|
||||||
|
struct cdev cdev;
|
||||||
|
u8 buffer[MYPIPE_BUFFER_SIZE];
|
||||||
|
size_t write_pos;
|
||||||
|
size_t read_pos;
|
||||||
|
size_t data_len;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This could be an array if we want multiple cdev
|
||||||
|
static struct mypipe_data pdata;
|
||||||
|
|
||||||
|
static struct class *mypipedev_class = NULL;
|
||||||
|
|
||||||
|
// Protects the buffer
|
||||||
|
static DEFINE_MUTEX(mypipe_lock);
|
||||||
|
// These are two wait_queue_head_t for storing tasks that are waiting for
|
||||||
|
// writing/ reading
|
||||||
|
static DECLARE_WAIT_QUEUE_HEAD(read_queue);
|
||||||
|
static DECLARE_WAIT_QUEUE_HEAD(write_queue);
|
||||||
|
|
||||||
|
static int mypipe_open(struct inode *inode, struct file *file) {
|
||||||
|
printk("Mypipe opened.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mypipe_release(struct inode *inode, struct file *file) {
|
||||||
|
printk("Mypipe released.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mypipe_read(struct file *filep, char __user *buf, size_t size,
|
||||||
|
loff_t *offset) {
|
||||||
|
ssize_t ret = 0;
|
||||||
|
|
||||||
|
if (iminor(file_iniode(filep)) != MYPIPE_MINOR_OUT) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire the lock. If not acquired and interrupted by signal, let kernel
|
||||||
|
// to try restart the syscall
|
||||||
|
if (mutex_lock_interruptible(&mypipe_lock)) {
|
||||||
|
return -ERESTARTSYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We currently have no data, but we can wait for possible new to write in
|
||||||
|
while (pdata.data_len == 0) {
|
||||||
|
mutex_unlock(&mypipe_lock);
|
||||||
|
if (filep->f_flags & O_NONBLOCK) {
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
if (wait_event_interruptible(read_queue, pdata.data_len > 0)) {
|
||||||
|
return -ERESTARTSYS;
|
||||||
|
}
|
||||||
|
// Woken, relock for checking the condition
|
||||||
|
if (mutex_lock_interruptible(&mypipe_lock)) {
|
||||||
|
return -ERESTARTSYS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// data_len > 0
|
||||||
|
size_t to_read = min(size, pdata.data_len);
|
||||||
|
size_t until_end = min(to_read, MYPIPE_BUFFER_SIZE - pdata.read_pos);
|
||||||
|
if (copy_to_user(buf, pdata.buffer + pdata.read_pos, until_end)) {
|
||||||
|
ret = -EFAULT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_read > until_end) {
|
||||||
|
if (copy_to_user(buf + until_end, pdata.buffer, to_read - until_end)) {
|
||||||
|
ret = -EFAULT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pdata.read_pos = (pdata.read_pos + to_read) % MYPIPE_BUFFER_SIZE;
|
||||||
|
pdata.data_len -= to_read;
|
||||||
|
ret = to_read;
|
||||||
|
|
||||||
|
wake_up_interruptible(&write_queue);
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&mypipe_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mypipe_write(struct file *filep, char __user *buf, size_t size,
|
||||||
|
loff_t *offset) {
|
||||||
|
// This is very similar to read(), just write instead of read
|
||||||
|
|
||||||
|
ssize_t ret = 0;
|
||||||
|
|
||||||
|
if (iminor(file_indo(filep)) != MYPIPE_MINOR_IN) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mutex_lock_interruptible(&mypipe_lock)) {
|
||||||
|
return -ERESTARTSYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (pdata.data_len == MYPIPE_BUFFER_SIZE) {
|
||||||
|
mutex_unlock(&mypipe_lock);
|
||||||
|
if (filep->f_flags & O_NONBLOCK) {
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
if (wait_event_interruptible(write_queue,
|
||||||
|
pdata.data_len < MYPIPE_BUFFER_SIZE)) {
|
||||||
|
return -ERESTARTSYS;
|
||||||
|
}
|
||||||
|
if (mutex_lock_interruptible(&mypipe_lock)) {
|
||||||
|
return -ERESTARTSYS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t space_left = MYPIPE_BUFFER_SIZE - pdata.data_len;
|
||||||
|
size_t to_write = min(size, space_left);
|
||||||
|
size_t until_end = min(to_write, MYPIPE_BUFFER_SIZE - pdata.write_pos);
|
||||||
|
|
||||||
|
if (copy_from_user(pdata.buffer + pdata.write_pos, buf, until_end)) {
|
||||||
|
ret = -EFAULT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_write > until_end) {
|
||||||
|
if (copy_from_user(pdata.buffer, buf + until_end,
|
||||||
|
to_write - until_end)) {
|
||||||
|
ret = -EFAULT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pdata.write_pos = (pdata.write_pos + to_write) % MYPIPE_BUFFER_SIZE;
|
||||||
|
pdata.data_len += to_write;
|
||||||
|
ret = to_write;
|
||||||
|
|
||||||
|
wake_up_interruptible(&read_queue);
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&mypipe_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct file_operations mypipe_fops = {.owner = THIS_MODULE,
|
||||||
|
.open = mypipe_open,
|
||||||
|
.read = mypipe_read,
|
||||||
|
.write = mypipe_write,
|
||||||
|
.release = mypipe_release};
|
||||||
|
|
||||||
|
static int __init mypipe_init(void) {
|
||||||
|
int ret;
|
||||||
|
dev_t dev;
|
||||||
|
|
||||||
|
// Acquire a region of major, minor for us to use. The name appears in
|
||||||
|
// /proc/devices (and potentially /sys ?).
|
||||||
|
ret = alloc_chrdev_region(&dev, 0, MYPIPE_MINOR_COUNT, MYPIPE_DEVICE_NAME);
|
||||||
|
if (ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_major = MAJOR(dev);
|
||||||
|
|
||||||
|
// Create the class in sysfs. The name appears in /sys/class/<name>
|
||||||
|
mypipedev_class = class_create(MYPIPE_DEVICE_NAME);
|
||||||
|
if (IS_ERR(mypipedev_class)) {
|
||||||
|
ret = PTR_ERR(mypipedev_class);
|
||||||
|
goto del_cdev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a cdev in our pdata
|
||||||
|
cdev_init(&pdata.cdev, &mypipe_fops);
|
||||||
|
pdata.cdev.owner = THIS_MODULE;
|
||||||
|
|
||||||
|
// Register this newly created cdev to kernel. We only have one dev, so we
|
||||||
|
// can MKDEV(dev_major, 0).
|
||||||
|
|
||||||
|
// Also, we want to devices that one handles write and one read, so the last
|
||||||
|
// arg is not 1, but 2 (In this case, MYPIPE_MINOR_COUNT)
|
||||||
|
ret = cdev_add(&pdata.cdev, MKDEV(dev_major, 0), MYPIPE_MINOR_COUNT);
|
||||||
|
if (ret)
|
||||||
|
goto unregister;
|
||||||
|
|
||||||
|
// Create device node /dev/mypipe_in and /dev/mypipe_out
|
||||||
|
device_create(mypipedev_class, NULL, MKDEV(dev_major, MYPIPE_MINOR_IN),
|
||||||
|
NULL, "%s_in", MYPIPE_DEVICE_NAME);
|
||||||
|
device_create(mypipedev_class, NULL, MKDEV(dev_major, MYPIPE_MINOR_OUT),
|
||||||
|
NULL, "%s_out", MYPIPE_DEVICE_NAME);
|
||||||
|
|
||||||
|
printk(KERN_INFO "Wendy: module loaded\n");
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
del_cdev:
|
||||||
|
cdev_del(&pdata.cdev);
|
||||||
|
unregister:
|
||||||
|
unregister_chrdev_region(MKDEV(dev_major, 0), MYPIPE_MINOR_COUNT);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit mypipe_destroy(void) {
|
||||||
|
// This is basically the reverse operation of mypipe_init
|
||||||
|
device_destroy(mypipedev_class, MKDEV(dev_major, MYPIPE_MINOR_IN));
|
||||||
|
device_destroy(mypipedev_class, MKDEV(dev_major, MYPIPE_MINOR_OUT));
|
||||||
|
class_unregister(mypipedev_class);
|
||||||
|
class_destory(mypipedev_class);
|
||||||
|
cdev_del(&pdata.cdev);
|
||||||
|
unregister_chrdev_region(MKDEV(dev_major, 0), MYPIPE_MINOR_COUNT);
|
||||||
|
printk(KERN_INFO "Wendy: module unloaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(mypipe_init);
|
||||||
|
module_exit(mypipe_destroy);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
Reference in New Issue
Block a user