Operating Systems 2014F: Assignment 7: Difference between revisions
(2 intermediate revisions by the same user not shown) | |||
Line 26: | Line 26: | ||
# The put_user() call is used to put data into a buffer residing in userspace. It is important in general for the kernel to use put_user() rather than just directly writing to the pointer because it may not be correct or safe to do so, depending upon the architecture. On 32-bit x86, however, omitting put_user() still allows the function to "work" (although it is likely that accessing such a pointer is unsafe as it may cause kernel memory corruption if it refers to a writable data structure in kernel memory). | # The put_user() call is used to put data into a buffer residing in userspace. It is important in general for the kernel to use put_user() rather than just directly writing to the pointer because it may not be correct or safe to do so, depending upon the architecture. On 32-bit x86, however, omitting put_user() still allows the function to "work" (although it is likely that accessing such a pointer is unsafe as it may cause kernel memory corruption if it refers to a writable data structure in kernel memory). | ||
# You simply change the umask assignment in ones_devnode() to be 0666 rather than 0444. This function is assigned to the devnode property of the ones_devnode struct when the module is initialized. When setting up the device inode for the ones device, it will create it with default values and then call this function to customize it. We can't be sure exactly when this function is called, or even how many times it is called; we just know that when the device inode is needed, this function will be called to customize it. (Note the pattern of the mask is simply a standard UNIX permission mask, with each octal value specifying read, write and execute bits for the user, the group, and everyone else.) | # You simply change the umask assignment in ones_devnode() to be 0666 rather than 0444. This function is assigned to the devnode property of the ones_devnode struct when the module is initialized. When setting up the device inode for the ones device, it will create it with default values and then call this function to customize it. We can't be sure exactly when this function is called, or even how many times it is called; we just know that when the device inode is needed, this function will be called to customize it. (Note the pattern of the mask is simply a standard UNIX permission mask, with each octal value specifying read, write and execute bits for the user, the group, and everyone else.) | ||
# | # Goto statements are commonly used in the Linux kernel because the kernel must be very careful to recover from errors and C does not support exceptions. The common pattern in Linux kernel programming is for the main logic of a function to call many helper functions, with the return value of each being checked by an if statement. If a helper function fails in any way a goto is made to the appropriate error handler section at the end of the function. Note the goto statements can naturally support the reuse of error handlers, with errors earlier in the function going to the end of the error handler portion (as there are fewer things to "undo"), while gotos deeper in the function go to earlier parts of the error handler section. While it is possible to rewrite kernel code to not use goto's, in general the resulting code will be much harder to understand and/or less efficient. | ||
# | # The kernel uses functions like printk() rather than standard C library functions such as printf() because standard libraries are written to make use of system calls and the general functionality of userspace. The kernel, as the code that implements system calls and that very functionality, cannot rely on such functionality already existing. Some C library code can be imported (e.g., some string handling functions) and those are simply duplicated in the Linux kernel codebase. Functions like printf() however make no sense as they assume the availability of file descriptors. In contrast printk() uses the kernel's logging facility which can be configured to write output to log files and text consoles, things that aren't available to standard userspace programs. (Userspace can instead use syslog.) | ||
Latest revision as of 16:56, 11 December 2014
Please submit the answers to the following questions via CULearn by just before midnight (11:55 PM) on Thursday, November 20, 2014. There 20 points in 8 questions.
Submit your answers as a single text file named "<username>-comp3000-assign7.txt" (where username is your MyCarletonOne username). The first four lines of this file should be "COMP 3000 Assignment 7", your name, student number, and the date of submission. You may wish to format your answers in Markdown to improve their appearance.
No other formats will be accepted. Submitting in another format will likely result in your assignment not being graded and you receiving no marks for this assignment. In particular do not submit a zip file, MS Word, or OpenOffice file as your answers document!
You can turn in one source file (included in your text file) answering questions 3 and 4. Just make it clear what changes were made for answering each question either in the comments of the code or in separate explanatory text.
Don't forget to include what outside resources you used to complete each of your answers, including other students, man pages, and web resources. You do not need to list help from the instructor, TA, or information found in the textbook.
Note the following questions make reference to the code from Tutorial 7.
- [2] What kind of errors do Linux kernel "Oops" messages report? How can the kernel generate these errors (rather than just crashing)? Be sure to give a specific example (with code) of something that would generate an Oops error.
- [2] What is the purpose of the ones_fops struct? How are its values used?
- [2] What is the importance of the put_user() call in ones_read()? If you omit it and instead just assign directly (e.g., "*buf++ = '1'"), does the module still work? (Explain what happens if the module uses or doesn't use put_user() to store values to buf and how that relates to the importance of put_user()).
- [2] How do you change the code of the ones module so that /dev/ones is readable and writable by all users (without manually changing the permissions on the special file /dev/ones)? Why does this code control the permissions of this file? (To answer explain briefly when and how the code specifying the permission is called.)
- [2] Many places in the Linux kernel source code use goto statements (like ones_init()). Is this an acceptable practice in the context of kernel programming? Explain briefly the role of the majority of these goto statements and how they compare to the alternatives in C code.
- [2] Why does the kernel code use functions like printk() rather than printf or other functions from the standard C library? Explain briefly.
- [4] Modify the module to create a new device "/dev/onelines" that inserts a line break ('\n') every 78 characters. Be sure to do the right thing when the output isn't divisible by 78, i.e., handle partial lines properly. (Note the sysfs entries for oneslines should also appear in /sys/class/comp3000.)
- [4] Modify the module to create a new device "/dev/repeatchar" that produces a character device that can input exactly one character. (If more than one character is given to it, it only uses the first.) It then outputs that character an unbounded number of times. By default, it should output "1" so it behave exactly as "ones" did before.
Solutions
Solutions for this assignment were discussed in Lecture 23.
- Linux kernel "Oops" messages are used to report non-fatal kernel errors. Normally Oops messages are generated when the CPU encounters an exception such as attempting to access an invalid memory address (pointer) or doing an illegal mathematical operation (such as divide by zero).
- The ones_fops structure specifies what functions the kernel should call when performing file operations on the ones device file. The whole purpose of "special" files is to cause standard file API calls such as read and write to do new, custom things. If a function isn't specified for a given operation the kernel will use a default handler which may just return "operation not implemented".
- The put_user() call is used to put data into a buffer residing in userspace. It is important in general for the kernel to use put_user() rather than just directly writing to the pointer because it may not be correct or safe to do so, depending upon the architecture. On 32-bit x86, however, omitting put_user() still allows the function to "work" (although it is likely that accessing such a pointer is unsafe as it may cause kernel memory corruption if it refers to a writable data structure in kernel memory).
- You simply change the umask assignment in ones_devnode() to be 0666 rather than 0444. This function is assigned to the devnode property of the ones_devnode struct when the module is initialized. When setting up the device inode for the ones device, it will create it with default values and then call this function to customize it. We can't be sure exactly when this function is called, or even how many times it is called; we just know that when the device inode is needed, this function will be called to customize it. (Note the pattern of the mask is simply a standard UNIX permission mask, with each octal value specifying read, write and execute bits for the user, the group, and everyone else.)
- Goto statements are commonly used in the Linux kernel because the kernel must be very careful to recover from errors and C does not support exceptions. The common pattern in Linux kernel programming is for the main logic of a function to call many helper functions, with the return value of each being checked by an if statement. If a helper function fails in any way a goto is made to the appropriate error handler section at the end of the function. Note the goto statements can naturally support the reuse of error handlers, with errors earlier in the function going to the end of the error handler portion (as there are fewer things to "undo"), while gotos deeper in the function go to earlier parts of the error handler section. While it is possible to rewrite kernel code to not use goto's, in general the resulting code will be much harder to understand and/or less efficient.
- The kernel uses functions like printk() rather than standard C library functions such as printf() because standard libraries are written to make use of system calls and the general functionality of userspace. The kernel, as the code that implements system calls and that very functionality, cannot rely on such functionality already existing. Some C library code can be imported (e.g., some string handling functions) and those are simply duplicated in the Linux kernel codebase. Functions like printf() however make no sense as they assume the availability of file descriptors. In contrast printk() uses the kernel's logging facility which can be configured to write output to log files and text consoles, things that aren't available to standard userspace programs. (Userspace can instead use syslog.)