From 196ed7c71c5d0cb47ea98c2fc0652261b7ec3f1a Mon Sep 17 00:00:00 2001 From: paul-loedige Date: Mon, 31 Oct 2022 11:57:52 +0900 Subject: [PATCH] Code finished --- Code/IOCTL_Client/chardev.h | 51 +++++++ Code/IOCTL_Client/userspace_ioctl.c | 104 +++++++++++++ Code/Kernel_Module/Makefile | 9 ++ Code/Kernel_Module/chardev.h | 51 +++++++ Code/Kernel_Module/chardev2.c | 227 ++++++++++++++++++++++++++++ README.md | 5 + output.txt | Bin 0 -> 1093 bytes run.sh | 15 ++ 8 files changed, 462 insertions(+) create mode 100755 Code/IOCTL_Client/chardev.h create mode 100644 Code/IOCTL_Client/userspace_ioctl.c create mode 100644 Code/Kernel_Module/Makefile create mode 100644 Code/Kernel_Module/chardev.h create mode 100644 Code/Kernel_Module/chardev2.c create mode 100644 output.txt create mode 100644 run.sh diff --git a/Code/IOCTL_Client/chardev.h b/Code/IOCTL_Client/chardev.h new file mode 100755 index 0000000..78ea814 --- /dev/null +++ b/Code/IOCTL_Client/chardev.h @@ -0,0 +1,51 @@ +/* + * chardev.h - the header file with the ioctl definitions. + * + * The declarations here have to be in a header file, because they need + * to be known both to the kernel module (in chardev2.c) and the process + * calling ioctl() (in userspace_ioctl.c). + */ + +#ifndef CHARDEV_H +#define CHARDEV_H + +#include + +/* The major device number. We can not rely on dynamic registration + * any more, because ioctls need to know it. + */ +#define MAJOR_NUM 100 + +/* Set the message of the device driver */ +#define IOCTL_SET_MSG _IOW(MAJOR_NUM, 0, char *) +/* _IOW means that we are creating an ioctl command number for passing + * information from a user process to the kernel module. + * + * The first arguments, MAJOR_NUM, is the major device number we are using. + * + * The second argument is the number of the command (there could be several + * with different meanings). + * + * The third argument is the type we want to get from the process to the + * kernel. + */ + +/* Get the message of the device driver */ +#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *) +/* This IOCTL is used for output, to get the message of the device driver. + * However, we still need the buffer to place the message in to be input, + * as it is allocated by the process. + */ + +/* Get the n'th byte of the message */ +#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int) +/* The IOCTL is used for both input and output. It receives from the user + * a number, n, and returns message[n]. + */ + +/* The name of the device file */ +#define DEVICE_FILE_NAME "char_dev" +#define DEVICE_PATH "/dev/char_dev" + +#endif + diff --git a/Code/IOCTL_Client/userspace_ioctl.c b/Code/IOCTL_Client/userspace_ioctl.c new file mode 100644 index 0000000..cc2cce0 --- /dev/null +++ b/Code/IOCTL_Client/userspace_ioctl.c @@ -0,0 +1,104 @@ +/* userspace_ioctl.c - the process to use ioctl's to control the kernel module + * + * Until now we could have used cat for input and output. But now + * we need to do ioctl's, which require writing our own process. + */ + +/* device specifics, such as ioctl numbers and the + * major device file. */ +#include "chardev.h" + +#include /* standard I/O */ +#include /* open */ +#include /* close */ +#include /* exit */ +#include /* ioctl */ + +/* Functions for the ioctl calls */ + +int ioctl_set_msg(int file_desc, char *message) +{ + int ret_val; + + ret_val = ioctl(file_desc, IOCTL_SET_MSG, message); + + if (ret_val < 0) { + printf("ioctl_set_msg failed:%d\n", ret_val); + } + + return ret_val; +} + +int ioctl_get_msg(int file_desc) +{ + int ret_val; + char message[100] = { 0 }; + + /* Warning - this is dangerous because we don't tell + * the kernel how far it's allowed to write, so it + * might overflow the buffer. In a real production + * program, we would have used two ioctls - one to tell + * the kernel the buffer length and another to give + * it the buffer to fill + */ + ret_val = ioctl(file_desc, IOCTL_GET_MSG, message); + + if (ret_val < 0) { + printf("ioctl_get_msg failed:%d\n", ret_val); + } + printf("get_msg message:%s", message); + + return ret_val; +} + +int ioctl_get_nth_byte(int file_desc) +{ + int i, c; + + printf("get_nth_byte message:"); + + i = 0; + do { + c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++); + + if (c < 0) { + printf("\nioctl_get_nth_byte failed at the %d'th byte:\n", i); + return c; + } + + putchar(c); + } while (c != 0); + + return 0; +} + +/* Main - Call the ioctl functions */ +int main(void) +{ + int file_desc, ret_val; + char *msg = "Message passed by ioctl\n"; + + file_desc = open(DEVICE_PATH, O_RDWR); + if (file_desc < 0) { + printf("Can't open device file: %s, error:%d\n", DEVICE_PATH, + file_desc); + exit(EXIT_FAILURE); + } + + ret_val = ioctl_set_msg(file_desc, msg); + if (ret_val) + goto error; + ret_val = ioctl_get_nth_byte(file_desc); + if (ret_val) + goto error; + ret_val = ioctl_get_msg(file_desc); + if (ret_val) + goto error; + + close(file_desc); + return 0; +error: + close(file_desc); + exit(EXIT_FAILURE); +} + diff --git a/Code/Kernel_Module/Makefile b/Code/Kernel_Module/Makefile new file mode 100644 index 0000000..46cac8a --- /dev/null +++ b/Code/Kernel_Module/Makefile @@ -0,0 +1,9 @@ +obj-m += chardev2.o + +PWD := $(CURDIR) + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/Code/Kernel_Module/chardev.h b/Code/Kernel_Module/chardev.h new file mode 100644 index 0000000..78ea814 --- /dev/null +++ b/Code/Kernel_Module/chardev.h @@ -0,0 +1,51 @@ +/* + * chardev.h - the header file with the ioctl definitions. + * + * The declarations here have to be in a header file, because they need + * to be known both to the kernel module (in chardev2.c) and the process + * calling ioctl() (in userspace_ioctl.c). + */ + +#ifndef CHARDEV_H +#define CHARDEV_H + +#include + +/* The major device number. We can not rely on dynamic registration + * any more, because ioctls need to know it. + */ +#define MAJOR_NUM 100 + +/* Set the message of the device driver */ +#define IOCTL_SET_MSG _IOW(MAJOR_NUM, 0, char *) +/* _IOW means that we are creating an ioctl command number for passing + * information from a user process to the kernel module. + * + * The first arguments, MAJOR_NUM, is the major device number we are using. + * + * The second argument is the number of the command (there could be several + * with different meanings). + * + * The third argument is the type we want to get from the process to the + * kernel. + */ + +/* Get the message of the device driver */ +#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *) +/* This IOCTL is used for output, to get the message of the device driver. + * However, we still need the buffer to place the message in to be input, + * as it is allocated by the process. + */ + +/* Get the n'th byte of the message */ +#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int) +/* The IOCTL is used for both input and output. It receives from the user + * a number, n, and returns message[n]. + */ + +/* The name of the device file */ +#define DEVICE_FILE_NAME "char_dev" +#define DEVICE_PATH "/dev/char_dev" + +#endif + diff --git a/Code/Kernel_Module/chardev2.c b/Code/Kernel_Module/chardev2.c new file mode 100644 index 0000000..89ebed6 --- /dev/null +++ b/Code/Kernel_Module/chardev2.c @@ -0,0 +1,227 @@ +/* + * chardev2.c - Create an input/output character device + */ + +#include +#include +#include +#include +#include +#include +#include /* We are doing kernel work */ +#include /* Specifically, a module */ +#include + +#include "chardev.h" +#define SUCCESS 0 +#define DEVICE_NAME "char_dev" +#define BUF_LEN 80 + +enum { + CDEV_NOT_USED = 0, + CDEV_EXCLUSIVE_OPEN = 1, +}; + +/* Is the device open right now? Used to prevent concurrent access into + * the same device + */ +static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED); + +/* The message the device will give when asked */ +static char message[BUF_LEN + 1]; + +static struct class *cls; + +/* This is called whenever a process attempts to open the device file */ +static int device_open(struct inode *inode, struct file *file) +{ + pr_info("device_open(%p)\n", file); + + try_module_get(THIS_MODULE); + return SUCCESS; +} + +static int device_release(struct inode *inode, struct file *file) +{ + pr_info("device_release(%p,%p)\n", inode, file); + + module_put(THIS_MODULE); + return SUCCESS; +} + +/* This function is called whenever a process which has already opened the + * device file attempts to read from it. + */ +static ssize_t device_read(struct file *file, /* see include/linux/fs.h */ + char __user *buffer, /* buffer to be filled */ + size_t length, /* length of the buffer */ + loff_t *offset) +{ + /* Number of bytes actually written to the buffer */ + int bytes_read = 0; + /* How far did the process reading the message get? Useful if the message + * is larger than the size of the buffer we get to fill in device_read. + */ + const char *message_ptr = message; + + if (!*(message_ptr + *offset)) { /* we are at the end of message */ + *offset = 0; /* reset the offset */ + return 0; /* signify end of file */ + } + + message_ptr += *offset; + + /* Actually put the data into the buffer */ + while (length && *message_ptr) { + /* Because the buffer is in the user data segment, not the kernel + * data segment, assignment would not work. Instead, we have to + * use put_user which copies data from the kernel data segment to + * the user data segment. + */ + put_user(*(message_ptr++), buffer++); + length--; + bytes_read++; + } + + pr_info("Read %d bytes, %ld left\n", bytes_read, length); + + *offset += bytes_read; + + /* Read functions are supposed to return the number of bytes actually + * inserted into the buffer. + */ + return bytes_read; +} + +/* called when somebody tries to write into our device file. */ +static ssize_t device_write(struct file *file, const char __user *buffer, + size_t length, loff_t *offset) +{ + int i; + + pr_info("device_write(%p,%p,%ld)", file, buffer, length); + + for (i = 0; i < length && i < BUF_LEN; i++) + get_user(message[i], buffer + i); + + /* Again, return the number of input characters used. */ + return i; +} + +/* This function is called whenever a process tries to do an ioctl on our + * device file. We get two extra parameters (additional to the inode and file + * structures, which all device functions get): the number of the ioctl called + * and the parameter given to the ioctl function. + * + * If the ioctl is write or read/write (meaning output is returned to the + * calling process), the ioctl call returns the output of this function. + */ +static long +device_ioctl(struct file *file, /* ditto */ + unsigned int ioctl_num, /* number and param for ioctl */ + unsigned long ioctl_param) +{ + int i; + long ret = SUCCESS; + + /* We don't want to talk to two processes at the same time. */ + if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN)) + return -EBUSY; + + /* Switch according to the ioctl called */ + switch (ioctl_num) { + case IOCTL_SET_MSG: { + /* Receive a pointer to a message (in user space) and set that to + * be the device's message. Get the parameter given to ioctl by + * the process. + */ + char __user *tmp = (char __user *)ioctl_param; + char ch; + + /* Find the length of the message */ + get_user(ch, tmp); + for (i = 0; ch && i < BUF_LEN; i++, tmp++) + get_user(ch, tmp); + + device_write(file, (char __user *)ioctl_param, i, NULL); + break; + } + case IOCTL_GET_MSG: { + loff_t offset = 0; + + /* Give the current message to the calling process - the parameter + * we got is a pointer, fill it. + */ + i = device_read(file, (char __user *)ioctl_param, 99, &offset); + + /* Put a zero at the end of the buffer, so it will be properly + * terminated. + */ + put_user('\0', (char __user *)ioctl_param + i); + break; + } + case IOCTL_GET_NTH_BYTE: + /* This ioctl is both input (ioctl_param) and output (the return + * value of this function). + */ + ret = (long)message[ioctl_param]; + break; + } + + /* We're now ready for our next caller */ + atomic_set(&already_open, CDEV_NOT_USED); + + return ret; +} + +/* Module Declarations */ + +/* This structure will hold the functions to be called when a process does + * something to the device we created. Since a pointer to this structure + * is kept in the devices table, it can't be local to init_module. NULL is + * for unimplemented functions. + */ +static struct file_operations fops = { + .read = device_read, + .write = device_write, + .unlocked_ioctl = device_ioctl, + .open = device_open, + .release = device_release, /* a.k.a. close */ +}; + +/* Initialize the module - Register the character device */ +static int __init chardev2_init(void) +{ + /* Register the character device (atleast try) */ + int ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops); + + /* Negative values signify an error */ + if (ret_val < 0) { + pr_alert("%s failed with %d\n", + "Sorry, registering the character device ", ret_val); + return ret_val; + } + + cls = class_create(THIS_MODULE, DEVICE_FILE_NAME); + device_create(cls, NULL, MKDEV(MAJOR_NUM, 0), NULL, DEVICE_FILE_NAME); + + pr_info("Device created on /dev/%s\n", DEVICE_FILE_NAME); + + return 0; +} + +/* Cleanup - unregister the appropriate file from /proc */ +static void __exit chardev2_exit(void) +{ + device_destroy(cls, MKDEV(MAJOR_NUM, 0)); + class_destroy(cls); + + /* Unregister the device */ + unregister_chrdev(MAJOR_NUM, DEVICE_NAME); +} + +module_init(chardev2_init); +module_exit(chardev2_exit); + +MODULE_LICENSE("GPL"); + diff --git a/README.md b/README.md index ac6fc8f..acaed3d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # Homework_Lesson_4 +## Assignment +Modify the kernel module you created in the #3 Homework to introduce the ioctl mechanism that sets and gets messages. +Compile and run the program. +You should also write a user-space program that calls ioctl(). +Submit the source code and the screen shot of the result of execution displayed by the dmesg command. \ No newline at end of file diff --git a/output.txt b/output.txt new file mode 100644 index 0000000000000000000000000000000000000000..7589f9d28868435dcc75ace401ba84cfeaec4cf8 GIT binary patch literal 1093 zcmb7@OK;RL5XU+DQ_K;hMQb~89xL@yxK>D1oDhl}dp3!hmu&Cr+cSx`T?7@-Tz>y~ zdB)z^Ppx*^1ax5W*ctRY1|VR-ftbRUyxXJM^3@9A^2|pPDu$!?YM5#CQ;)ak9ELMl zIUJ2=AUF)2HzX41#`zE!$P)H76P$ygSt@GCgvdbmUNa#JpaYmlGNNaq;BfGiT@*lV zzdhcQEEm^ON8>ZeC7AiJ9lRr*teWi3GAT2#ZeWtms=Lu5B_uhtopt&|vVhY`kJUW6 zKvoIkalnraj_5kWWXe|u2?6?WCQ7J%LrmbLgK6!w7%4a$rhv1XmLf${&qSJj_QV_v zF8%`g&Ml$@7l?|39dy;Arfem^YU-dQ*lrk1=t(Cq+Ne@!=58`=H#2tMPv+^@U#rFj z>PtXcIPZ0XyN5`awfAVDz5v~oUf|u5J^1FIiEp0cj(vZaM)+a^(2a%IseC6C&?3nn z@Oj-TFb;K~aVT&7N4#w6E2Y`BG%jQ{%{ad2*U#oc@s#HuUhDZ=;mLIY|ESzNA0^P1 tqA ../../output.txt +echo "==================================================" >> ../../output.txt +cd ../IOCTL_Client +gcc userspace_ioctl.c -o userspace_ioctl.o +sudo ./userspace_ioctl.o >> ../../output.txt +echo "==================================================" >> ../../output.txt +cd ../Kernel_Module +sudo rmmod chardev2 +sleep 5 +cd ../../ +sudo dmesg -T -l info | tail -5 >> output.txt +echo "Done" \ No newline at end of file