CS 111 Operating Systems Principles (Spring 13) – Lecture 05 Scribe Notes

Lecture given by Prof. Paul Eggert on 4/16/2013. Prepared by Dara Sok.

Orthogonality:

In Computer Science, orthogonality means changing in one component of the system does not affect the others. By increasing the orthogonality in the system, the propagation of effect is minimized.

fork() under POSIX:

Create a child process by copying the parent process, except the following points:

  • the child has its own pid
  • Accumulated execution time
  • Pending Signals
  • File locks
  • File descriptors

The shorter the above list, the more orthogonal the system becomes.

What about execvp(“/bin/cat”, …)?

This is the opposite of fork(). execvp() replaces the calling process with a new one, and keep all the things in above, while replacing: program, stack, heap, instruction pointer.

So why don’t we have:

int posix_spawnvp(
    pid_t *restrict pid, //by fork
    char const *restrict file, //by execvp
    posix_spawn_file_actions_t const *file_actions, //everything else
    posix_spawnattr_t const *restrict attrp, //everything else
    char * const restrict argv, //by execvp
    char * const restrict envp); //by execvp

//the restrict keyword means the caller promise no aliasing (for optimization).
//That is only that pointer can access to the object which it points.

posix_spawnvp() can spawns a child process in both Windows and Linux, but it’s non-orthogonal, since it depends on both fork() and execvp().

Access to Device and Files:

  • It’s slow, so we don’t worry too much about CPU overhead.
  • Robustness is more of an issue, so we’re less likely to want application to talk directly to devices.
  • More variety, more complexity, and more need for abstraction in OS.

 

Major Dichotomy for Devices:

Nework, keyboard, mouse VS Disk, flash
Stream Random access
Spontaneous data generation Request/Response
Infinite Limited capacity

File I/O:

ssize_t read(int fd, void *buf, size_t len);
ssize_t write(int fd, void *buf, size_t len);
//read/write up to len bytes from file descriptor fd into the buffer starting at buf.
​ssize_t pread(int fd, void *buf, size_t len, off_t offset);
//reads up to len bytes from fd at offset offset into buffer starting at buf. The file offset is not changed.
off_t lseek(int fd, off_t offset, int flags);
//re-positions the offset of a file associated with the file descriptor fd with directive flags.
int open(char const * filename, int flags, ... );
//open will try to find the first unused file descriptor and return it on success, or returns -1 on error.
/*some possible flags are: O_RDONLY==0 for read-only access
                           O_WRONLY==1 for write-only access
                           O_RDWR==2 for read/write access
Flags that are not in Linux yet: O_EXEC, O_SEARCH
*/

int close(int fd);
//return EBADF if fd isn't a valid open file descriptor.
//return EIO if an I/O error occurred; write did not successfully write buffer to disk.
//duplicate a file descriptor:
int dup(int oldfd); //finds a first unused fd number return the new fd, which point to the same file as oldfd.
int dup2(int oldfd, int newfd); //make newfd a copy of oldfd. Close the newfd first if necessary.

Process Table

|----------------------------|
|                            |
|          <--fds-->         |
|   fd#: 0 1 2 3 4 5 6...    |
|       | | | |.| | | |...   |
|              :------------------>[file description]----> actual file
|                            |
|----------------------------|

Special fds:
fd 0: stdin
fd 1: stdout
fd 2: stderr

What can go wrong?

  • Use one that closed (errno == EBADF)
  • Use one that closed and reopen other file with the same fd.
  • I/O error (errno == EIO)
  • Forget to close fd; fd leak
  • Read but no data yet (from stream device). Read hangs or read return error after a period depending on the device.
  • Read but EOF. By convention, read return 0.

Consider the following commands:

touch grade
ls -l grade
-rw-rw-rw   0
chmod 600 grade
ls -l
-rw------   0

The problem here is that if someone open the grade file after line 1 and before line 4, then the person will still have read/write permission after the file’s permission is changed by chmod. This is because the permission is checked when a file is open, and the permission flags is stored in the file’s associated file descriptor when it’s open.

More examples:

Process 1: cat file1
Process 2: rm file1 //while process 1 is catting the file.
In this example, rm did not affect the cat process.

//create a temporary file
char name[100];
sprintf(name, "/tmp/sort%ld", getpid());
int fd = open(name, O_RDONLY | O_CREATE| O_TRUNC, 0666);
if (fd<0) error();
if (unlink(name)!=0) error();
//If a file is unlinked before it's fd is closed, then if the process write data into the fd, the file might become nameless and takes up space.

Race conditions:

A timing-dependent error, where two processes are trying access/modify the same data, which might causes desired behavior or incorrect result.

Consider trying to create a temporary file:

char buf[100]
generate_random_filename(buf);
while (stat(buf, &st)==0){
  generate_random_filename(buf);
  int fd = open(buf, O_RDONLY | O_CREATE| O_TRUNC, 0666);
}

But this method has a race condition, since other process can still open the file between the while condition and open.

Now we add the O_EXCL flag, and change the code as follow:

while ((fd=open(buf, O_RDONLY | O_CREATE| O_TRUNC | O_EXCL, 0600))<0){
  generate_random_filename(buf);
}

Now, the file will surely be created and open exclusively and so the race condition is avoided.