Operating Systems 2018F: Assignment 4: Difference between revisions

From Soma-notes
No edit summary
 
(2 intermediate revisions by the same user not shown)
Line 23: Line 23:
# [2] What system calls does memoryll make when performing file operations on behalf of the kernel?  Give one concrete example showing how things work. (Hint: you won't be able to strace memoryll as a regular user.)
# [2] What system calls does memoryll make when performing file operations on behalf of the kernel?  Give one concrete example showing how things work. (Hint: you won't be able to strace memoryll as a regular user.)
# '''[BONUS 5]''' Implement mmap for the remember device.  Show your implementation and explain how it works.
# '''[BONUS 5]''' Implement mmap for the remember device.  Show your implementation and explain how it works.
==Solutions==
# alloc_pages() allocates memory immediately
# with an anonymous mmap, memory is allocated as it is accessed (on demand, not on allocation)
# A key advantage of lazy memory allocation is it allows better use of available memory resources.  A key disadvantage is there can come a time when all allocations cannot be fulfilled, potentially causing the system to fail in ungraceful ways (e.g., having to kill processes at random)
# It will return "Buffer too small." on the first read.  On the second read it will return the same thing as the offset is not changed by the code and so is still zero.
# Memory is dynamically allocated when data is written to /dev/remember (by remember_write).  Data is freed also by remember_write (before allocating new memory) and is freed when the module is unloaded (in remember_exit).
# As written, only the contents of the most recent write system call will be remembered.  This happens because the previously allocated pages are thrown away and reallocated at the start of remember_write() (lines 119 and 120).  (In class we demonstrated concurrent writes will cause the module to fail because the module does not lock its storage.  It is correct to point this out as an answer to this question but that isn't the intended answer.)
# getattr() is called whenever the kernel needs information on a file's attributes (really, an inode's attributes).  Thus the system calls that return information on inodes, such as lstat and fstat, cause getattr to be called.  This can be verified by running the command line utility "stat" on a file.  You'll see that getattr is called right after lookup - and an strace of the command will reveal that it makes an lstat call on the file.
# You could change setattr() to make it impossible to change the timestamps on a file, as any changes to the attributes of a file stored in an inode have to go through this function (for the memoryll filesystem).
# The uid of fusermount will be the uid of the regular user (e.g., in the VMs it will be the uid of the student user, uid 1000).  The euid, however, will be 0, the uid of root.
# A process's effective uid (euid) is the user that the kernel uses when determining whether an operation is authorized or not.  Normally the euid and uid are equal; however, when setuid binaries are execve'd the euid of a process is set to the owner of the binary (and the regular uid is left unchanged).  This allows regular users to start programs that run with different levels of privilege (e.g., that run as root).
# memoryll makes read and writev system calls when communicating with the kernel.  An example is below.  It was obtained by running memoryll under strace (running as root).  Based on the output to standard out and the memoryll code doing messages at the start of the functions, in the following the read is the kernel requesting a getattr of inode 1, and then memoryll is replying with the writev.  To do a more detailed analysis we'd have to decode the protocol by looking at the documentation or the code for fuse.
21760 read(3, "8\0\0\0\3\0\0\0\22\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 135168) = 56
21760 write(1, "getattr: 1\n", 11)      = 11
21760 writev(3, [{iov_base="x\0\0\0\0\0\0\0\22\0\0\0\0\0\0\0", iov_len=16}, {iov_base="\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., iov_len=104}], 2) = 120

Latest revision as of 16:13, 23 November 2018

Please submit the answers to the following questions via CULearn by 2:30 PM on Wednesday, November 21, 2018. There are 20 points in 11 questions (and one bonus question worth 5 points).

Submit your answers as a single text file named "<username>-comp3000-assign4.txt" (where username is your MyCarletonOne username). The first four lines of this file should be "COMP 3000 Assignment 4", 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 an MS Word or OpenOffice file as your answers document!

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.

Questions

  1. [1] Does alloc_pages() in the Linux kernel immediately allocate memory, or is memory allocated when it is accessed?
  2. [1] Does an anonymous mmap system call allocate memory immediately, or is the memory allocated as it is accessed?
  3. [2] What is one key advantage of lazy memory allocation? What is one key disadvantage?
  4. [2] Assume we have modified the remember module so it allocates 16K (4 pages) at a time, and assume we have stored in /dev/remember 16K copies of the letter "a".
    • As written, what will a read of /dev/remember return if we use a 4K buffer, starting at the beginning of the file?
    • What will happen if we do a second read, again with a 4K buffer (on the same open file)?
  5. [2] When is memory dynamically allocated in the remember module? When is it freed?
  6. [2] As written, what happens when multiple write calls are made to /dev/remember? What is remembered? Why?
  7. [2] What system call causes getattr() in memoryll.py to be called (on files in a memoryll filesystem)? How did you verify this?
  8. [2] What function in memoryll would you have to change to make it impossible to change file timestamps? Why?
  9. [2] When you run fusermount via execve, what euid does fusermount's process have? What uid does it have?
  10. [2] What is a process's effective uid (euid)? Why is the euid not always equal to its uid?
  11. [2] What system calls does memoryll make when performing file operations on behalf of the kernel? Give one concrete example showing how things work. (Hint: you won't be able to strace memoryll as a regular user.)
  12. [BONUS 5] Implement mmap for the remember device. Show your implementation and explain how it works.

Solutions

  1. alloc_pages() allocates memory immediately
  2. with an anonymous mmap, memory is allocated as it is accessed (on demand, not on allocation)
  3. A key advantage of lazy memory allocation is it allows better use of available memory resources. A key disadvantage is there can come a time when all allocations cannot be fulfilled, potentially causing the system to fail in ungraceful ways (e.g., having to kill processes at random)
  4. It will return "Buffer too small." on the first read. On the second read it will return the same thing as the offset is not changed by the code and so is still zero.
  5. Memory is dynamically allocated when data is written to /dev/remember (by remember_write). Data is freed also by remember_write (before allocating new memory) and is freed when the module is unloaded (in remember_exit).
  6. As written, only the contents of the most recent write system call will be remembered. This happens because the previously allocated pages are thrown away and reallocated at the start of remember_write() (lines 119 and 120). (In class we demonstrated concurrent writes will cause the module to fail because the module does not lock its storage. It is correct to point this out as an answer to this question but that isn't the intended answer.)
  7. getattr() is called whenever the kernel needs information on a file's attributes (really, an inode's attributes). Thus the system calls that return information on inodes, such as lstat and fstat, cause getattr to be called. This can be verified by running the command line utility "stat" on a file. You'll see that getattr is called right after lookup - and an strace of the command will reveal that it makes an lstat call on the file.
  8. You could change setattr() to make it impossible to change the timestamps on a file, as any changes to the attributes of a file stored in an inode have to go through this function (for the memoryll filesystem).
  9. The uid of fusermount will be the uid of the regular user (e.g., in the VMs it will be the uid of the student user, uid 1000). The euid, however, will be 0, the uid of root.
  10. A process's effective uid (euid) is the user that the kernel uses when determining whether an operation is authorized or not. Normally the euid and uid are equal; however, when setuid binaries are execve'd the euid of a process is set to the owner of the binary (and the regular uid is left unchanged). This allows regular users to start programs that run with different levels of privilege (e.g., that run as root).
  11. memoryll makes read and writev system calls when communicating with the kernel. An example is below. It was obtained by running memoryll under strace (running as root). Based on the output to standard out and the memoryll code doing messages at the start of the functions, in the following the read is the kernel requesting a getattr of inode 1, and then memoryll is replying with the writev. To do a more detailed analysis we'd have to decode the protocol by looking at the documentation or the code for fuse.
21760 read(3, "8\0\0\0\3\0\0\0\22\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 135168) = 56
21760 write(1, "getattr: 1\n", 11)      = 11
21760 writev(3, [{iov_base="x\0\0\0\0\0\0\0\22\0\0\0\0\0\0\0", iov_len=16}, {iov_base="\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., iov_len=104}], 2) = 120