ASST1: Robots and System Calls

Version 1.2.2: New FAQ entries clarifying some underspecified assignment rules. Also fix a silly typo id == id2/id != id2 in a FAQ entry.

Version 1.1: Clarified that submission scripts have been pushed to bugfix branch.

Version 1.0: Submission format clarified and doc questions released.

Table of Contents

Due Dates and Mark Distribution

Due Date & Time: 4pm (16:00), Jun 30 (Tue, Week 5)

Marks: Worth 10% of the marks for the course.

Introduction

In this assignment you will implement some simple system calls, including the kernel side that handles the system call and the user side that performs the system call. The goal of the exercise is to pass the user's system call requests through to a simple simulated device that can only be accessed by the OS kernel.

The device you will be controlling is called a Rob161.

The device is owned by the local (fictional) UNSW computer systems and dance performance club. The club acquired it thanks to an attractive deal offered to student organisations. They planned to use it for a big performance earlier this year. That project didn't go well. The robot comes with some pre-programmed demo movements that look promising but aren't much use in planning a performance. It also has a wifi-based control program that goes through a web interface, which proved to be clumsy and slow. Some computing students tried to write a custom OS plug-in to drive the hardware, which tended to freeze the host OS. Some electrical students then tried to sidestep the built-in computer boards and drive the robot motors directly, which ended with one of the robot batteries on fire. Oops. The project was left on hold while the students focused on their performance.

Now that a new term is starting, some of the students have a little more time and want to examine the Rob161 challenge again. In a fortunate coincidence, most of these students are enrolled in the COMP3231 course, and are learning about the OS/161 system, which happens to support the crucial control board of the robot. Let's see if we can help them out!

Setting Up Your Assignment

We assume after ASST0 that you now have some familiarity with setting up for OS/161 development. The following is a brief setup guide. If you need more info about how to go step through these steps, refer back to ASST0.

Obtain the ASST1 distribution with git

From ASST1 onwards, we will provide you with your own git project on a CSE-owned gitlab instance.

You should be able to view your git project at this URL (replace z888 with your zID):

https://gitlab.cse.unsw.edu.au/coursework/COMP3231/26T2/assigns/z8888888-asst1.git

You can clone this repository either using HTTPS or setting up an SSH key. We recommend SSH, but it does require setting up a key. The gitlab instance will give HTTPS and SSH URLs for cloning under the "Code" button.

We recommend you clone your sources to asst1-src:

cd ~/cs3231/
git clone <HTTPS or SSH URL> asst1-src

Merging Bugfixes

We have encountered at least one issue with the initially provided sources and pushed a bugfix. We recommend you merge in our bugfix like this:

cd ~/cs3231/asst1-src        # (or wherever you cloned the sources to)
git fetch origin            # (fetch any new code pushed to gitlab)
git merge origin/bugfixes    # (merge current state with the bugfix branch)

It's possible that we'll add more bugfixes or similar in the remaining assignment time, and you might need to repeat this process.

Configure OS/161 for Assignment 1

Configure your new sources as follows.

% cd ~/cs3231/asst1-src
% ./configure && bmake && bmake install

The above steps configure everything and build the userland binaries. We will rebuild some of the userland binaries later in the project.

We also need to configure and build the kernel. The procedure for configuring a kernel is the same as in ASST0, except you will use the ASST1 configuration file:

% cd ~/cs3231/asst1-src/kern/conf
% ./config ASST1

You should now see an ASST1 directory in the kern/compile directory.

Building ASST1

When you built OS/161 for ASST0, you ran bmake in compile/ASST0. In ASST1, you run bmake from (you guessed it) compile/ASST1.

% cd ../compile/ASST1
% bmake depend
% bmake
% bmake install

If you are told that the compile/ASST1 directory does not exist, make sure you ran config for ASST1.

Tip: Once you start modifying the OS/161 kernel, you can quickly rebuild and re-install with the following command sequence. It will install the kernel if the build succeeds. There should be no need to repeat the depend step unless you want to add more source files to the kernel, which we do not recommend for assignment 1.

% bmake && bmake install

Check sys161.conf

The sys161.conf should be already be installed in the ~/cs3231/root directory from assignment 0. If not, fetch it from this link (sys161.conf) or using these commands:

% cd ~/cs3231/root
% wget http://cgi.cse.unsw.edu.au/~cs3231/26T2/assignments/asst1/sys161.conf

Run the kernel

Run the previously built kernel:

% cd ~/cs3231/root
% sys161 kernel
sys161: System/161 release 2.0.8, compiled Feb 25 2019 09:34:40

OS/161 base system version 2.0.3
(with locks&CVs solution)
Copyright (c) 2000, 2001-2005, 2008-2011, 2013, 2014
President and Fellows of Harvard College.  All rights reserved.

Put-your-group-name-here's system version 0 (ASST1 #1)

16220k physical memory available
Device probe...
lamebus0 (system main bus)
emu0 at lamebus0
ltrace0 at lamebus0
ltimer0 at lamebus0
beep0 at ltimer0
rtclock0 at ltimer0
lrandom0 at lamebus0
random0 at lrandom0
lser0 at lamebus0
con0 at lser0

cpu0: MIPS/161 (System/161 2.x) features 0x0
OS/161 kernel [? for menu]:

Kernel menu commands and arguments to OS/161

Your solutions to ASST1 will be tested (and automarked) by running OS/161 with command line arguments that correspond to the menu options in the OS/161 boot menu.

Caution!

Do not change these menu option strings!

Here are some examples of using command line arguments to select OS/161 menu items:

sys161 kernel "at;bt;q"

This is the same as starting up with sys161 kernel, then running "at" at the menu prompt (invoking the array test), then when that finishes running "bt" (bitmap test), then quitting by typing "q".

sys161 kernel "q"

This is the simplest example. This will start the kernel up, then quit as soon as it's finished booting. Try it yourself with other menu commands. Remember that the commands must be separated by semicolons (";").

Essential to this assignment is the p command, which runs an initial user-level program. You can, for instance, run the add test like this:

sys161 kernel "p testbin/add"

The above test is expected to mostly fail, and nearly all the others will also fail, reporting that we haven't implemented some system calls. In assignment 2, we will add some standard system calls to OS/161, so that many of these tests pass.

For now, we're interested in some special-purpose tests, named left_wave, double_wave, queries and async_scale. We'll introduce each of them below.

System Calls in OS/161

System calls are special operations performed by user-level software that cause an entry (trap) to the kernel. The kernel saves the user's state to a trap-frame object and calls to a function called syscall in a MIPS-specific file called syscall.c. Find that file now, and look over it if you haven't already. You will need to add cases to this file to support the new system calls we will write in this assignment.

The collection of available system calls is defined in a file called kern/include/kern/syscall.h. This defines many constants, such as SYS_read, which identify system call numbers. This header is shared by the kernel and user-level build processes, allowing user tasks to tell the kernel which system call they want to perform. The prototypes of system calls such as read are spread across various user-level headers, such as the unistd.h header. The bodies of all the system calls are in an auto-generated assembly file, which we will explain later.

The Rob161 robot is an instance of an "auxiliary I/O device". For assignment 1 we have added a collection of the special numbers from the aux-I/O hardware manual to the header aux_io_codes.h. That header is available as kern/aux_io_codes.h in both user-level and kernel-level C code. You should find that file in the kernel sources and have a quick look at the various kinds of device subsystems, queries and device actions available.

We may test your assignment with a different version of the robot attached.

Caution!

Do not depend on adjustments to the rob161.c file, or change the kernel interface to it defined in aux_io.h.

The simulated robot defined in rob161.c knows about some test-sequences that can be used to test your user-level code is correctly performing some action. You might consider modifying the device to make the checks easier to pass. Don't do that. Also, don't make too many assumptions about the implementation of the device. We will test your code against a slightly modified version. The assumptions you can make about the interface are explained in the general aux-I/O interface header kern/include/aux_io.h.

Coding the Assignment

We know: you've been itching to get to the coding. Well, you've finally arrived!

Part 1: Dispatching the aux_query and aux_action System Calls

Marks: 40% of this assignment (30% code tests and 10% doc questions)

The first task is to implement the aux_query and aux_action system calls.

To do this, you need to add new system call numbers for them to the syscall.h file we looked at above. You can choose the system call numbers, but they should be different from all existing system calls. You should use the standard naming convention, e.g. SYS_aux_query.

Note: This is unusual. In the remaining assignments, you will be adding implementations of standard UNIX system calls, which are already enumerated and prototyped at user-level, and documented in the usual manual pages. In this assignment, you are adding a non-standard system call for this feature. Their specification is below.

The provided simulated robot actually implements the underlying query and action operations. Your job is to map the syscall interface to the device interface, including handling some possible error conditions.

Handling new system calls

You need to attach some new system call handling code to deal with a SYS_aux_query system call, etc. We recommend you have a look at how the example system call SYS___time/sys___time is handled. There is space set up for you to write new system call functionality in kern/syscall/aux_io.c.

Edit: There are two functions already in aux_io.c, which you may edit. You don't need to edit the function with "async" in its name until Part 4. The function with "setup" in its name is called once at system startup, before any other syscall functionality is called. You can use it to allocate or initialise anything that you need to do before handling system calls.

Examples of auxiliary queries and actions

The user-level programs in userland/testbin/left_wave and userland/testbin/queries contain calls to the aux_query and aux_action syscalls. However the calls are commented out, as they will not compile with the provided kernel and user library.

Adding the system call numbers above, and rebuilding the entire system, causes the bodies of the system call functions to be generated in a special assembly file. You also need to add an appropriate signature for these system calls somewhere. There is space to do that in userland/include/aux_io_calls.h. You have to figure out appropriate type signatures. The specifications below may help. Once you have an appropriate type signature, the provided programs should compile with the syscalls commented back in.

Recall that we can rebuild the user-level programs with bmake && bmake install in the top-level asst1-src directory.

These test binaries can then be run with sys161. At this point this should produce diagnostic messages about unknown system calls from the kernel. The main part of this task is to provide the system calls.

Specification of aux_query

The aux_query system call takes two arguments, subsystem, and query. The subsystem parameter specifies which subsystem of the connected aux-I/O device to manipulate. The query parameter specifies what kind of thing to query.

The result of a query operation will be a non-negative value. Like many other system calls, aux_query can fail and return a negative value, and also generate an error code, which the user will receive via the errno mechanism.

The error codes used by the kernel and returned to the user are listed in kern/include/kern/errno.h.

Specification of aux_action

The aux_action system call takes three arguments, subsystem, command and argument. It instructs the underlying device subsystem to take some action. You can pass the command and argument directly to the relevant device subsystem.

The device actions return no output on success. The system call does return an integer, which can be negative in the case that an error code has been raised.

Design

The system call interface required at user level does not exactly match the device interface, especially the way device subsystems are managed. This is a common scenario. It is part of the job of the OS code to bridge between the user-facing interface and the device-facing interface.

Caution!

It is important you do not change either interface. Your implementation should compile with the rob161.c device as provided, and also with the provided user-level programs (with the system calls un-commented).

Re-building the kernel

Once you have implemented aux_query and aux_action, you should be able to build and install your modified kernel via bmake && bmake install in the compile/ASST1 directory.

The provided test binaries should then run and make system calls. They will fail to complete their test sequences until we fix some errors though.

Part 2: Testing the System Calls at User-Level

Marks: 20% of this assignment (15% code tests and 5% doc questions)

Recall that we can rebuild the user-level programs with bmake && bmake install in the top-level asst1-src directory.

It's time to think about some of the complications of driving the "Rob161" robot.

The queries test program does some simple queries of the status of the robot and its environment. As is clear in the comments in its source file, some of the things that need querying haven't been checked yet. Figure out the appropriate encoding of the remaining queries and add them in, then rebuild user-level and run the test binary. The simulated robot should acknowledge that the full query sequence has been completed.

The left_wave test program tries to wave the robot's left arm. This simple command strategy will fail and jam up the robot's control motors. The problem is something to do with voltage and capacitance in the robot's subsystems fluctuating. To safely move the robot arm, we need to check (query) that the circuit is ready. If the circuit isn't ready, we need to wait for the robot status to update by performing a pause operation on the robot core. If the circuit reports that it is ready, the action is safe to perform, but may still fail to complete because of transient power issues. After taking such an action, we should check (query) the action result (SYSTEM_ACTION_RESULT) of the subsystem. A positive result indicates success.

Fix the left_wave program to perform the provided sequence of actions, ensuring that each action is performed safely and completely before moving on to the next action. The simulated robot should then acknowledge that the full query sequence has been completed.

The double_wave program should wave both robot arms, with each arm waved according to the sequence from left_wave. You need to implement this program. Again, the simulated robot will tell you when it is correctly implemented.

Part 3: Improving the Double-Wave

Marks: 20% of this assignment (15% code tests and 5% doc questions)

In Part 2 you implemented the double-wave program, so that it completes the double wave test, i.e. each arm gets waved eventually.

Unless you peeked ahead, you have very likely done this the easy way, and have performed each of the steps of the two waves in some pre-determined order. The resulting double-wave will take about twice as long as a single wave. Note that the simulated robot counts the number of total actions (including pauses) and reports them when a sequence completed.

Note that it is possible to complete this move sequence much faster, if you pay attention to cases where one arm subsystem is not able to make a move but the other is ready. Picking the schedule that suits the hardware state, rather than fixing it in the software design, should allow the robot to finish much sooner, though it makes the software more complicated to write.

Adjust your double wave program to complete in 32 or less steps, as counted by the provided simulated robot.

Note

If you have a working implementation of parts 1 and 2, it might be a good idea to commit it with git before you break anything, so that you can compare to or revert to that version if you get stuck.

Part 4: Async Commands and Concurrency

Marks: 20% of this assignment (15% code tests and 5% doc questions)

The robot is moving faster than it did, but it is still acting too slowly when multiple subsystems are in use. The final task is to implement an alternative asynchronous style of interaction, which will hopefully let the control program manage even more of the robot subsystems efficiently.

To activate asynchronous mode on a subsystem, set its synchronous mode parameter to 0 (false) using the SYSTEM_SET_SYNC_MODE operation. This operation must be performed on the ROBOT_SYNC_SYSTEM subsystem before any other. Code to do this for one subsystem is already present in the partly-completed async_scale program already performs one of these steps.

This mode makes some commands non-blocking. When a non-blocking command is issued, the call to the device completes immediately, and the device works in the background to complete the request. The OS system call should also complete immediately. The user program can then issue parallel requests on other robot subsystems. It is an error to send more than one command to the one subsystem.

Edit: spec clarification: It is a user-level error to send more than one async command to the one subsystem. It is not specified what the result of this will be. The kernel shouldn't crash, but might stop performing system calls or managing the device correctly.

The user program can wait for its commands to complete with a special query operation, consulting the READY_SUBSYSTEM value on the ROBOT_SYNC_SYSTEM. This system call waits for at least one subsystem to be finished, and returns the ID of one of the subsystems that is finished.

Note that this query variant must be implemented specifically by the OS, differently to the other query types. If this query is run on the underlying simulated device, it will respond immediately, like all other queries. The system call thread should be blocked if none of the commands have completed yet, which requires synchronisation or thread management that is the responsibility of the OS and not the device.

When the device finishes an asynchronous task, it will notify the OS. This appears via a call to the aux_io_async_notify function, which was left unimplemented so far. Once the robot is in async mode, this function may be called. The call to this function should unblock a blocked system call thread if one exists.

Note that the aux_io_async_notify function will be called from separate kernel threads to the one handling system calls. This means that concurrency is now an issue. Part of this task is to implement this function and manage the concurrency issues with some kind of synchronisation process.

The other part of the task is to exercise this asynchronous functionality from a user level program. The partially finished async_scale program contains a description of how to raise the core frequency of a robot limb through the frequencies of a C major chord. Complete the program to perform this command sequence on all four robot limbs, its arms and its legs. This should be run in parallel using asynchronous operations to command all limbs at once. The program should, for instance, issue all four initial commands prior to waiting for any of them.

As with the previous parts, the simulated robot will give a diagnostic message when the task is complete. If you have seen that message, well done on getting to this point, though it might still be worth further testing of the robustness of your assignment, especially with regard to concurrency issues.

Documenting Your Assignment

Each year, in addition to submitting your code for the assignments, we ask you to submit some further documentation.

This year's format is simple: we want you to answer a few questions about each of the assignment parts.

Save your answers in a text file called "doc.txt" and submit it with your code bundle.

Here are the questions:

Part 1:

  • Which trapframe registers are relevant to the result of an "aux_query" system call?
  • What effect should an OS/161 "aux_action" system call have if the attempt to look up the requested subsystem returns NULL?

Part 2:

  • Implementing Part 2 creates some tests for your Part 1 work. Did you have to fix or revise any of your Part 1 kernel code?
  • The build system for OS/161 user-level test binaries is set up quite differently to the kernel build system. What reasons can you think of why they are so separate and different?

Part 3:

  • We expect that your Part 3 implementation of double-wave can perform its action sequence in multiple different orderings. What state did you have to store to track where it was up to?
  • Could we avoid managing additional state if we could use multiple threads instead? If so, how, and if not, why not?

Part 4:

  • When a process waits for the next ready subsystem (by querying READY_SUBSYSTEM) and has to wait, what mechanism did you use to have the syscall thread wait? How is it resumed?
  • When a query of the next ready subsystem is made, and no subsystem is ready, do we need to handle the case where the wait ends because of a command that has not yet been issued?

We expect that each of these questions can be answered in under 100 words, and that your answers in total will be 500 words or less. Some marks will be given for correctness, and some subjective marks for being clear and concise. This scheme will be clarified slightly in the FAQ soon.

A skeleton file for answering the questions has been pushed to the "bugfixes" branch of the repository.

Submitting

The submission is via the "give" system at CSE. It would be nice to integrate this further with the "gitlab" system but we won't get that infrastructure built this year.

To copy the "git" state to "give", we build a git bundle (a single file that contains the commit history of the repository), copy it to CSE, and submit via "give".

To simplify the process a little, we have provided a python script that does the bundle and copy. Merge in the bugfixes branch and the submit.py script will be in the submission/ directory.

Bundling and Giving

The python script performs these steps. If you need to or want to do this by hand, here are the stepes.

Further notes on this submission process can be found on the Wiki .

Bundle the git history (assumes the branch to submit is "main"):

% git bundle create asst1.bundle main

If already at CSE, give these files:

% give cs3231 asst1 asst1.bundle doc.txt

If not, copy the bundle and the doc-file to CSE then give:

% scp asst1.bundle doc.txt login.cse.unsw.edu.au:.
% ssh -t login.cse.unsw.edu.au give cs3231 asst1 asst1.bundle doc.txt

Both the instructions above and the python script will leave the bundle in your working directory, and a copy of the bundle and the documentation in your CSE home directory if you weren't already at CSE. You may want to clean them up, there's no ongoing need for the bundle.

Submission Output

The submission system will do some simple checks.

Warning

Don't ignore the submission system! If your submission fails the simple test in the submission process, you probably submitted the wrong thing, and you may not receive any marks.

Once you've submitted, you're done.

Even though the submission should represent all the work you have done and the changes you have made to the supplied code, occasionally students do something "ingenious" and don't submit what they thought. So always keep your git repository so that you may recover your assignment should something go wrong. We recommend you copy your work back to the gitlab instance with git push and keep your local copy for safe keeping.

Note on the advanced part

A previous version of this spec document mentioned an advanced part. There is no advanced part for assignment 1 in 2026, which is a variation from 2025. We'll explain the way advanced parts work, and their role in the 3891/9283 Extended-OS course variants, in assignment 2.

FAQ

Submit questions about the assignment on the forum. A summary of any especially significant answers will appear here.

The aux_io.h interface and rob161.c

Your syscall implementation should manage the robot through the aux_io.h interface. You shouldn't need to know that this is the version implemented in rob161.c or try to directly call any of that version's functions.

It was mentioned above that the aux_query system call will return non-negative values. You may assume that the unsigned integers returned by calling query operations on aux-I/O devices will remain non-negative when cast from unsigned integers to signed integers.

The meaning of the READY_SUBSYSTEM query

This is probably the most important spec clarification.

It seems that there are multiple interpretations of what the READY_SUBSYSTEM query on the ROBOT_SYNC_SYSTEM does. We agree that the initial specification above didn't clarify the behaviour enough.

A number of students have come to the right "intuitive understanding" of what the system call is for and therefore how it must behave, but others are confused by issues of timing.

The goal of the async interface is to allow a control program to start multiple actions and then wait for any of them to complete. If the program starts some number N of actions on separate subsystems, and then waits N times, it should receive the IDs of all of those subsystems (in some unspecified order).

Consider this program, for instance:

/* Assume both legs already in async mode. */
aux_action(ROBOT_LEFT_LEG, SYSTEM_TURN_AROUND, 1);
aux_action(ROBOT_RIGHT_LEG, SYSTEM_TURN_AROUND, 2);
id = aux_query(ROBOT_SYNC_SYSTEM, READY_SUBSYSTEM);
assert(id == ROBOT_LEFT_LEG || id == ROBOT_RIGHT_LEG);
id2 = aux_query(ROBOT_SYNC_SYSTEM, READY_SUBSYSTEM);
assert(id2 == ROBOT_LEFT_LEG || id2 == ROBOT_RIGHT_LEG);
assert(id != id2);
/* Done. */
exit();

We should expect that this kind of program does not fail any of the assertions, and does reach the final exit(), assuming that the system was in async mode, and that no other async operations have been started.

Note that this program won't work with the provided rob161.c implementation as SYSTEM_TURN_AROUND isn't implemented.

This example has some implications about which ready subsystems can be reported by any call to a READY_SUBSYSTEM query.

The autogenerated syscall bodies

We promised above to explain the auto-generated assembly file that contains all the system call bodies. This is part of the OS/161 build system. It reads the kern/syscall.h include file, and for each symbol that starts with SYS_, defines a system call body. Note, as we mentioned in lectures, that the bodies are all the same, they just load the syscall number to the correct register and jump to __syscall.

The generated file is called syscalls.S. It only exists once you've built everything, and its contents won't clearly explain what is going on, but it will clarify where the system call bodies are. To reiterate the Part 1 spec: you don't have to implement a body of these functions, it will be autogenerated. You do have to give them a signature so that C functions can call them.

The maximum number and ID of subsystems

The spec above says that you shouldn't assume the provided rob161.c simulated implementation of the robot. What about the interface, in particular the subsystem and command codes in aux_io_codes.h?

You should handle the case where user-level calls robot subsystems and features that are not explicitly listed in aux_io_codes.h. However, you may assume that subsystem IDs are positive and that TEST_SYSTEM is the highest-numbered system, and also that the number of subsystems used in any one test is no higher than ROBOT_MAX_NUM_SUBSYSTEMS.