Operating Systems 2017F Lecture 16: Difference between revisions
Blanked the page |
No edit summary |
||
(6 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
==Video== | |||
The video from the lecture given on Nov. 7, 2017 [http://homeostasis.scs.carleton.ca/~soma/os-2017f/lectures/comp3000-2017f-lec16-09Nov2017.mp4 is now available]. | |||
==Code== | |||
[http://homeostasis.scs.carleton.ca/~soma/os-2017f/code/lec16/newgetpid.zip Code for download] | |||
==newgetpid.c== | |||
<source lang="C" line> | |||
/* 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"); | |||
</source> | |||
==Makefile== | |||
<source lang=make line> | |||
obj-m := newgetpid.o | |||
KDIR := /lib/modules/$(shell uname -r)/build | |||
PWD := $(shell pwd) | |||
default: | |||
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules | |||
</source> | |||
=== Additional Notes === | |||
What determines what files you can and cannot create?<br> | |||
* Ssh privileges <br> | |||
<br> | |||
* Anything that you can do as an ssh user you can do in the vm, just doing file operations<br> | |||
* Sshfs means when you do read and write system calls for programs in a directory it also does it on the remote machine<br> | |||
* Ssh is a good tool to access remote files locally, we will use it to edit modules<br> | |||
<br> | |||
Ones/newgetpid program:<br> | |||
* We want to extend its functionality in a specific way<br> | |||
* We want to access info about the process that made the system call<br> | |||
* Let's get current processes id<br> | |||
** Normally we would use getpid<br> | |||
** But we can't make system calls in kernel space<br> | |||
** But we can call the function that the system call uses or just copy the functionality<br> | |||
* Code for this is in "kernel/sys.c"<br> | |||
<br> | |||
Syscall_define0:<br> | |||
* Macro that expands<br> | |||
* Defines system calls with "getpid" and takes no arguments<br> | |||
<br> | |||
* Can use the code inside the function but not the function itself in the kernel<br> | |||
* Getpid returns a pid_t<br> | |||
* Instead of get_ones returning all those ones we want it to return the pid<br> | |||
* Lets try to get it to output a basic string with the pid<br> | |||
<br> | |||
How does printk work?<br> | |||
* Printk sends its output to the kernel log<br> | |||
* We changed the name to "newgetpid"<br> | |||
* How do we convert int to string to print the pid?<br> | |||
** Make a buffer, let's call it "message"<br> | |||
<br> | |||
Why does the pid keep incrementing each time we call "cat /dev/newgetpid"?<br> | |||
* "Cat" spawns a process so every time we do a fork we get a new pid<br> | |||
<br> | |||
Why can we use snprintf but not printf?<br> | |||
* We include "linux/kernel.h" which defines snprintf but not printf<br> | |||
* Printf assumes we have a standard output to print to<br> | |||
* Snprintf only needs character arrays for it to work<br> | |||
<br> | |||
* Read functionality uses an API<br> | |||
* Adding new functionality like "write" is easy, just look at the standard API and original kernel source<br> | |||
* All device files have their own custom read and write functions<br> | |||
<br> | |||
Why do we use goto's?<br> | |||
* C has no exception handling functionality so we implement our own<br> | |||
* Jumps to error paths: failed_devreg, failed_classreg, failed_chrdevreg (very important in the kernel)<br> | |||
* Kernel needs to be able to handle it's own errors<br> | |||
* Needs to free up allocated resources, "undo" everything<br> | |||
<br> | |||
* "." is the current directory<br> | |||
* ".." is the parent directory<br> | |||
** Introduces another hardlink<br> | |||
<br> | |||
* 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 |
Latest revision as of 08:38, 24 November 2017
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