Operating Systems 2017F Lecture 16
Video
The video from the lecture given on Nov. 7, 2017 is now available.
Code
newgetpid.c
/* Code derived from:
https://appusajeev.wordpress.com/2011/06/18/writing-a-linux-character-device-driver/
and
http://pete.akeo.ie/2011/08/writing-linux-device-driver-for-kernels.html
*/
#include <linux/module.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#define dbg(format, arg...) do { if (debug) pr_info(CLASS_NAME ": %s: " format, __FUNCTION__, ## arg); } while (0)
#define err(format, arg...) pr_err(CLASS_NAME ": " format, ## arg)
#define info(format, arg...) pr_info(CLASS_NAME ": " format, ## arg)
#define warn(format, arg...) pr_warn(CLASS_NAME ": " format, ## arg)
#define DEVICE_NAME "newgetpid"
#define CLASS_NAME "comp3000"
static struct class* newgetpid_class = NULL;
static struct device* newgetpid_device = NULL;
static int newgetpid_major;
static int newgetpid_open(struct inode *the_inode, struct file *f)
{
return 0;
}
static ssize_t newgetpid_read(struct file *f, char *buf, size_t len, loff_t *offset)
{
size_t i, msglen;
pid_t thepid;
char message[100];
if (*offset > 0) {
return 0;
}
thepid = task_tgid_vnr(current);
snprintf(message, 100, "Your PID is %d!\n", thepid);
msglen = strlen(message);
if (len < msglen) {
msglen = len;
}
for (i = 0; i < msglen; i++) {
put_user(message[i], buf++);
}
*offset = i;
return i;
}
static int newgetpid_release(struct inode *the_inode, struct file *f)
{
printk(KERN_ALERT "Newgetpid device closed\n");
return 0;
}
static struct file_operations newgetpid_fops = {
.open = newgetpid_open,
.read = newgetpid_read,
.release = newgetpid_release,
};
static char *newgetpid_devnode(struct device *dev, umode_t *mode)
{
if (mode)
*mode = 0444;
return NULL;
}
static int __init newgetpid_init(void)
{
int retval;
newgetpid_major = register_chrdev(0, DEVICE_NAME, &newgetpid_fops);
if (newgetpid_major < 0) {
err("failed to register device: error %d\n", newgetpid_major);
retval = newgetpid_major;
goto failed_chrdevreg;
}
newgetpid_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(newgetpid_class)) {
err("failed to register device class '%s'\n", CLASS_NAME);
retval = PTR_ERR(newgetpid_class);
goto failed_classreg;
}
newgetpid_class->devnode = newgetpid_devnode;
newgetpid_device = device_create(newgetpid_class, NULL, MKDEV(newgetpid_major, 0),
NULL, DEVICE_NAME);
if (IS_ERR(newgetpid_device)) {
err("failed to create device '%s'\n", DEVICE_NAME);
retval = PTR_ERR(newgetpid_device);
goto failed_devreg;
}
info("Newgetpid device registered using major %d.\n", newgetpid_major);
return 0;
failed_devreg:
class_unregister(newgetpid_class);
class_destroy(newgetpid_class);
failed_classreg:
unregister_chrdev(newgetpid_major, DEVICE_NAME);
failed_chrdevreg:
return -1;
}
static void __exit newgetpid_exit(void)
{
device_destroy(newgetpid_class, MKDEV(newgetpid_major, 0));
class_unregister(newgetpid_class);
class_destroy(newgetpid_class);
unregister_chrdev(newgetpid_major, "newgetpid");
info("Unloading Newgetpid module.\n");
return;
}
module_init(newgetpid_init);
module_exit(newgetpid_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Somayaji <soma@scs.carleton.ca>");
MODULE_DESCRIPTION("A write newgetpid character device module");
Makefile
obj-m := newgetpid.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
Additional Notes
What determines what files you can and cannot create?
- Ssh privileges
- Anything that you can do as an ssh user you can do in the vm, just doing file operations
- Sshfs means when you do read and write system calls for programs in a directory it also does it on the remote machine
- Ssh is a good tool to access remote files locally, we will use it to edit modules
Ones/newgetpid program:
- We want to extend its functionality in a specific way
- We want to access info about the process that made the system call
- Let's get current processes id
- Normally we would use getpid
- But we can't make system calls in kernel space
- But we can call the function that the system call uses or just copy the functionality
- Normally we would use getpid
- Code for this is in "kernel/sys.c"
Syscall_define0:
- Macro that expands
- Defines system calls with "getpid" and takes no arguments
- Can use the code inside the function but not the function itself in the kernel
- Getpid returns a pid_t
- Instead of get_ones returning all those ones we want it to return the pid
- Lets try to get it to output a basic string with the pid
How does printk work?
- Printk sends its output to the kernel log
- We changed the name to "newgetpid"
- How do we convert int to string to print the pid?
- Make a buffer, let's call it "message"
- Make a buffer, let's call it "message"
Why does the pid keep incrementing each time we call "cat /dev/newgetpid"?
- "Cat" spawns a process so every time we do a fork we get a new pid
Why can we use snprintf but not printf?
- We include "linux/kernel.h" which defines snprintf but not printf
- Printf assumes we have a standard output to print to
- Snprintf only needs character arrays for it to work
- Read functionality uses an API
- Adding new functionality like "write" is easy, just look at the standard API and original kernel source
- All device files have their own custom read and write functions
Why do we use goto's?
- C has no exception handling functionality so we implement our own
- Jumps to error paths: failed_devreg, failed_classreg, failed_chrdevreg (very important in the kernel)
- Kernel needs to be able to handle it's own errors
- Needs to free up allocated resources, "undo" everything
- "." is the current directory
- ".." is the parent directory
- Introduces another hardlink
- Introduces another hardlink
- In order to build kernel modules, you need to have the headers associated with the current kernel you're running
- Modules are specified to a particular version of the kernel