by Alan Chan and Emily Tran
- just like malloc/free, new/delete
To check the limit the number of file descriptors for a process, use:
$ulimit -a
f = open(...); // file assigned to file descriptor 19
if (f < 0) error();
if (close(f) != 0) error();
g = open(...); // file descriptor 19 is reused
close(f); // calling this will give an errno = EBADF
// note that read(f, ...) will also not work
f = open("foo", O_RDWR); // permissions set to read/write for file "foo"
read(f,...);
chmod 444 foo // user changes permissions of file to read only,
write(f,...); // but writing to file will still work since permissions aren't checked
rm foo // user tries to remove file here, but it will remain here until it has been closed
fork(); // this will work, but "foo" will not appear when "ls" is called on parent directory
f = opentemp(...);
PROS:
CONS:
int f = -1;
do {
char n[100000];
close(f);
create_random_name(n);
int f = open(n,O_RDRW);
} while(0 < f) // keep searching for unused (unopened) files to use
f = open(n, O_RDRW | O_CREAT | O_TRUNC, 0666);
However, a race condition can occur in the above situation if this program were to run simultaneously with another program that created the same file. Our solution is to replace the above line with this:
f = open(n, O_RDRW | O_CREAT | O_EXCL, 0666);
Flags used with open():
O_CREAT= Create file if it doesn't exist
O_TRUNC= Truncate the file if it exists
O_EXCL = Fail if the file already exists. Creates a temporary lock on the directory containing the created file.
fcntl(fd, F_SETLK // grab lock
F_SETLKW // grab lock, wait if necessary
F_GETLK // get the first lock which blocks a lock description
, p)
Locks are per-process (they are freed when a process exits)
Pipes are a bounded I/O buffer. The following command is an example of a pipe being used in the shell. It sorts files and directories in order according to size:
$ du | sort -n
This command equivalent to the following:
$ du > files.list
$ sort -rn files.list
Using a pipe is faster because it runs both commands in parallel. We will not encounter any race conditions with them, and we do not have to use any temporary files to save information.
What can go wrong with pipes?
$ du | less
Three things can happen:
Signals provide a way for asynchronous event handling. The operating system defines a set of signals, each of which are handled differently by the processes that receive them. When a process receives a signal, the operating system interrupts the normal flow of execution, and calls the relevant signal handler.
Signal handling is vulnerable to race conditions. For example, in this case:
signal(SIGPIPE, cleanup);
void cleanup(int p) {
malloc(19);
}
malloc() is not reentrant. That is, it cannot be safely executed concurrently if a second cleanup signal was delivered to a process that is in the middle of executing of the first cleanupsignal handling routine.