Useful Kernel Functions

Descriptions of kernel functions you may be unfamiliar with and may find helpful in implementing your lab. Implementation files are given as a helpful pointer for authoritative information on the workings of the functions, their locations are given relative to a linux kernel source tree, e.g. /usr/src/linux/. You may also find the Linux Cross Reference a helpful reference for Linux types and functions (search: Linux Cross Reference identifier search).

1. Mutex locking and unlocking. We provide our own version of Linux's spin_lock mutex type, because on the lab machines, Linux's implementation won't help you find bugs. But for Linux's implementation, see include/linux/spinlock.h.

void osp_spin_lock_init(osp_spinlock_t *mutex); Initialize a mutex
void osp_spin_lock(osp_spinlock_t *lock); Acquire a mutex (lock the mutex)
void osp_spin_unlock(osp_spinlock_t *lock); Release (unlock) the mutex

Do not call a scheduling function, such as schedule(), while holding a spin lock! That is a recipe for disaster: your entire kernel might deadlock.

2. Signals, implementation in include/linux/sched.h and kernel/signal.c.

int signal_pending(struct task_struct *process); Returns boolean indicating whether the given process has any pending signals. You will probably pass current for the process argument; current is Linux's name for the current process structure.
int send_sig(int sig, struct task_struct *p, int priv); Send a signal sig to the process specified by p (the linux kernel has a task_struct for each process), we'll set priv to 0. Returns < 0 on error.

3. Wait queues, implementation in kernel/wait.c and include/linux/wait.h. These functions change a process's state from TASK_RUNNABLE to a blocked state, and allow other processes or kernel code to wake up blocked processes.

DEFINE_WAIT(wait);
wait.func = &default_wake_function;
Defines a wait_queue_t object named wait. This object will be passed to prepare_to_wait and finish_wait.
void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state); Add the current task to the wait queue, using the named wait_queue_t object. The state member is the process's new state. Use TASK_INTERRUPTIBLE, which is a type of blocking that can be interrupted by signals. This function only adds the task to the queue; a later call to schedule will actually block. This allows you to avoid sleep/wakeup races: first, add the task to the wait queue; second, release any mutexes; and third, call schedule.

You may need to call prepare_to_wait_exclusive multiple times (if a thread wakes up, but needs to block again). The default_wake_function assignment, above, means the process retains its original position on the queue until it calls finish_wait.

The prepare_to_wait_exclusive function marks the current process as an "exclusive" waiter. The prepare_to_wait function marks the current process as a "nonexclusive" waiter. The wake_up function wakes up all non-exclusive waiters, plus the first exclusive waiter; wake_up_all wakes up all waiters, exclusive or not.
void finish_wait(wait_queue_head_t *q, wait_queue_t *wait); Remove the current task from the wait queue.
void wake_up_all(wait_queue_head_t *q); Wake up all tasks in the wait queue by setting their states to TASK_RUNNABLE.
void wake_up(wait_queue_head_t *q); Wake up the first sleeping exclusive task in the wait queue by setting its state to TASK_RUNNABLE. Also wakes up all non-exclusive tasks (prepare_to_wait). Skips over any awake exclusive tasks.
int waitqueue_active(wait_queue_head_t *q); Returns 1 if the wait queue has a process on it, 0 if the wait queue is empty.

4. CPU releasing/scheduling, implementation in kernel/timer.c.

void schedule(void); Release control of the CPU. If the current task's state is TASK_RUNNABLE, then the task might run again soon. If it is TASK_INTERRUPTIBLE, then the task will block until it is woken up by a signal or by wake_up_all.
long schedule_timeout(long timeout); Release control of the CPU for at most timeout time, which is measured in "jiffies". (There are HZ jiffies per second; often HZ is 1000.) Returns the amount of time remaining until timeout is returned, 0 is returned if the call timed out.

5. Block device requests, implementation in include/linux/blkdev.h.

unsigned int rq_data_dir(struct request *req); Returns the value indicating the given request's type, 0 for reads and 1 for writes.

6. File flags.

Your osprd_open and osprd_release functions will need to check whether your device file was opened for reading or writing. Given a struct file *filp variable:

(filp->f_mode & FMODE_WRITE) This expression is true (non-zero) if the file was opened for writing (O_WRONLY or O_RDWR).

If you'd like to check other file flags specified at open time, such as O_EXCL, for a design problem or something, do so like this:

(filp->f_flags & O_EXCL) This expression is true (non-zero) if the file was opened with the O_EXCL flag.

7. Helper functions for Lab 2.

osprd_device_t *file2osprd(struct file *filp); Linux's struct file object type corresponds to open files. This function takes an open file and returns the corresponding OSP ramdisk structure, osprd_device_t. This type is defined at the top of osprd.c. If filp is not actually an OSP ramdisk file, returns NULL.
void for_each_open_file(struct task_struct *task, void (*hook)(struct file *filp, osprd_info_t *d), osprd_info_t *d); Calls the hook function for each of task's open files. The function is called like hook(filp, d), where filp is the open file and d is the 3rd argument to for_each_open_file. You will probably pass current for the task argument; current is Linux's name for the current process structure.

The following example shows how to use for_each_open_file to count the number of ramdisk files that the current process, current, has open.

   typedef struct osprd_info {
       // ...
       int num_ramdisks_open;   // we add this variable for use by for_each_file
   } osprd_info_t;
 
   void count_ramdisks_hook(struct file *filp, osprd_info_t *d) {
       // This hook function will be passed to for_each_file.
       if (file2osprd(filp) != NULL)
           d->num_ramdisks_open++;
   }
 
   // ... somewhere else in the code, for example in osprd_open:
   d->num_ramdisks_open = 0;
   for_each_open_file(current, count_ramdisks_hook, d);
   eprintk("Number of ramdisks = %d\n", d->num_ramdisks_open);

Lab 3 functions

Lab 3 can use a couple other functions and data structures, particularly for quotas. Here's a list.

   long l;
   char *xxx;
   l = simple_strtol("100", &x, 10);
   // At this point, l == 100.