OS/161 Coding Conventions
The original Unix operating system has many descendants, some are lineal descendants, and some are more “inspired by”. These include
- HP-UX, Oracle Solaris, and IBM AIX, commercial operating systems for high-end servers and workstations, based on original code from Bell Labs/AT&T Unix.
- BSD Unix, a free Unix-like operating system that was developed at the University of California, Berkeley, originally using Bell Labs Unix code, but eventually rewritten to replace that code to settle licensing issues with AT&T (who owned Bell Labs).
- GNU/Linux, a free Unix-like operating system that was developed by Linus Torvalds in 1991, inspired by Minix, a Unix-like operating system developed by Andrew Tanenbaum in 1987, and leveraging code from the GNU project, a free software project started by Richard Stallman in 1983.
- macOS, a commercial Unix-like operating system developed by Apple, based on the Mach microkernel and BSD Unix. The core of macOS is open source and is called Darwin, with the kernel called XNU (X is Not Unix).
The code for many of these systems looks similar, with large amounts written in C (but some in assembly language for especially low-level code).
Whenever you work with a codebase, it is important to understand the coding conventions used in that codebase. This is true for your own code, but it's especially true for existing projects that you are contributing to. In this part of the lesson, we'll look at the coding conventions used in the OS/161 codebase so that you can write code that feels like it fits.
Indentation
The OS/161 codebase conventions are similar to those of Linux and BSD's “KNF” (Kernel Normal Form) style. The style is similar to the Linux kernel style, but with some differences.
Here's a function from proc.c in the OS/161 codebase as an example:
/*
* Create a proc structure.
*/
static
struct proc *
proc_create(const char *name)
{
struct proc *proc;
proc = kmalloc(sizeof(*proc));
if (proc == NULL) {
return NULL;
}
proc->p_name = kstrdup(name);
if (proc->p_name == NULL) {
kfree(proc);
return NULL;
}
proc->p_numthreads = 0;
spinlock_init(&proc->p_lock);
/* VM fields */
proc->p_addrspace = NULL;
/* VFS fields */
proc->p_cwd = NULL;
return proc;
}
A few things to note about the OS/161 style:
- 8-space indentation, compared to 4 spaces in CS 70.
- Continuation lines may be indented 4 spaces, but indented blocks are 8 spaces.
- Lines are limited to 80 characters in width.
- Function return type is on its own line, as are storage-class specifiers like
static. - Opening brace for functions is on its own line, not on the same line as the function name.
- Prefers
/* ... */comments to//comments. - Uses
struct proc *proc;instead ofstruct proc* proc;orstruct proc * proc;orproc* proc;.- In C, you have to put
structin front of types that are structs (you could usetypedefto avoid this and make a top-level type alias for your struct type, but OS/161 doesn't do that). - The compiler doesn't care about where the spaces are around the
*, and you read them according to CS 70's inside-out rule just the same, so it's just a convention, but one you'll have to get used to.
- In C, you have to put
Eight spaces is too many! How will I do deeply nested code?
If you're writing deeply nested code, it's probably too complex. Try to break it up into smaller functions.
What does
staticmean in this code?
When we declare a function as
static, it means that the function is only visible within the file it is declared in. This gives us a way to make helper functions private to a file.
Here's another example, this time from proc.h:
/*
* Process structure.
*
* Note that we only count the number of threads in each process.
* (And, unless you implement multithreaded user processes, this
* number will not exceed 1 except in kproc.) If you want to know
* exactly which threads are in the process, e.g. for debugging, add
* an array and a sleeplock to protect it. (You can't use a spinlock
* to protect an array because arrays need to be able to call
* kmalloc.)
*
* You will most likely be adding stuff to this structure, so you may
* find you need a sleeplock in here for other reasons as well.
* However, note that p_addrspace must be protected by a spinlock:
* thread_switch needs to be able to fetch the current address space
* without sleeping.
*/
struct proc {
char *p_name; /* Name of this process */
struct spinlock p_lock; /* Lock for this structure */
unsigned p_numthreads; /* Number of threads in this process */
/* VM */
struct addrspace *p_addrspace; /* virtual address space */
/* VFS */
struct vnode *p_cwd; /* current working directory */
/* add more material here as needed */
};
Notice that the fields of the struct all begin with the prefix p_. This is a common convention in C code to make it clear that the fields are part of the proc struct (and also because in C there is no namespace for struct fields, you can't have a field named name and a variable named name in the same scope).
Hay! What do you mean by “no namespace for struct fields”?
In C, struct fields are not in a separate namespace. They are part of the global scope, which means that the names of the fields within a struct must be unique not only within that struct but also across all other identifiers in the same scope. This requirement can sometimes lead to naming conflicts if we don't take care to ensure that field names are distinct from other variable or function names.
That's why a prefixing convention, like “
p_forprocfields”, is often used to avoid conflicts.
In fact, C code overall tends to use a variety of prefixing conventions. All the process related functions have the prefix
proc_, for example.
Staying in the Groove
As you code in the OS/161 codebase, the easiest way to stay consistent is to just look at how the preexisting code is written. If you're writing a new function, look at how other functions in the same file are written. If you're adding a new field to a struct, look at how other fields in the same struct are written.
Sounds like a plan.
(When logged in, completion status appears here.)