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.