The File System Interface

Let's look at the interface provided by the file system to the users, in this lesson.

We'll cover the following

Let’s now discuss the file system interface in more detail. We’ll start with the basics of creating, accessing, and deleting files. You may think this is straightforward, but along the way we’ll discover the mysterious call that is used to remove files, known as unlink(). Hopefully, by the end of this chapter, this mystery won’t be so mysterious to you!

Creating files

We’ll start with the most basic of operations: creating a file. This can be accomplished with the open system call; by calling open() and passing it the O_CREAT flag, a program can create a new file. Here is some example code to create a file called “foo” in the current working directory:

Press + to interact
int fd = open("foo", O_CREAT|O_WRONLY|O_TRUNC,
S_IRUSR|S_IWUSR);

ASIDE: THE creat() SYSTEM CALL

The older way of creating a file is to call creat(), as follows:

// option: add second flag to set permissions
int fd = creat("foo");

You can think of creat() as open() with the following flags: O_CREAT | O_WRONLY | O_TRUNC. Because open() can create a file, the usage of creat() has somewhat fallen out of favor (indeed, it could just be implemented as a library call to open()); however, it does hold a special place in UNIX lore. Specifically, when Ken Thompson was asked what he would do differently if he were redesigning UNIX, he replied: “I’d spell creat with an e.”

The routine open() takes a number of different flags. In this example, the second parameter creates the file (O_CREAT) if it does not exist, ensures that the file can only be written to (O_WRONLY), and, if the file already exists, truncates it to a size of zero bytes thus removing any existing content (O TRUNC). The third parameter specifies permissions, in this case making the file readable and writable by the owner.

One important aspect of open() is what it returns: a file descriptor. A file descriptor is just an integer, private per process, and is used in UNIX systems to access files; thus, once a file is opened, you use the file descriptor to read or write the file, assuming you have permission to do so. In this way, a file descriptor is a capability“Capability-Based Computer Systems” by Henry M. Levy. Digital Press, 1984. Available: http://homes.cs.washington.edu/ ̃levy/capabook. An excellent overview of early capability-based systems., i.e., an opaque handle that gives you the power to perform certain operations. Another way to think of a file descriptor is as a pointer to an object of type file; once you have such an object, you can call other “methods” to access the file, like read() and write() (we’ll see how to do so below).

As stated above, file descriptors are managed by the operating system on a per-process basis. This means some kind of simple structure (e.g., an array) is kept in the proc structure on UNIX systems. Here is the relevant piece from the xv6 kernel“The xv6 Operating System” by Russ Cox, Frans Kaashoek, Robert Morris, Nickolai Zeldovich. From: https://github.com/mit-pdos/xv6-public. As mentioned before, a cool and simple Unix implementation. We have been using an older version (2012-01-30-1-g1c41342) and hence some examples in the book may not match the latest in the source.:

Press + to interact
struct proc {
...
struct file *ofile[NOFILE]; // Open files
...
};

A simple array (with a maximum of NOFILE open files) tracks which files are opened on a per-process basis. Each entry of the array is actually just a pointer to a struct file, which will be used to track information about the file being read or written; we’ll discuss this further in the coming lessons.

Get hands-on with 1400+ tech skills courses.