Appendix C

User System Calls

1. Introduction

This Appendix gives details about the User System Calls - that is, the calls made from user programs to the Operating System. It is your job in building an Operating System to be able to handle these system calls.

These calls are defined in only very general terms. One of your jobs is to tighten down various ambiguities to the best of your ability. Such ambiguities should be spelled out so that it's possible to see the choices you've made. These are the only commands which a user program can execute that will affect OS502.

2. System Call Parameters

These are the system call parameters as defined in the include files. It's recommended that you use only the name in your code, and avoid at all costs employing the actual number of the call.

SYSNUM_MEM_READ
SYSNUM_MEM_WRITE
SYSNUM_READ_MODIFY
SYSNUM_GET_TIME_OF_DAY
SYSNUM_SLEEP
SYSNUM_CREATE_PROCESS
SYSNUM_GET_PROCESS_ID
SYSNUM_TERMINATE_PROCESS
SYSNUM_SUSPEND_PROCESS
SYSNUM_RESUME_PROCESS
SYSNUM_CHANGE_PRIORITY
SYSNUM_SEND_MESSAGE
SYSNUM_RECEIVE_MESSAGE
SYSNUM_DISK_READ
SYSNUM_DISK_WRITE
SYSNUM_DEFINE_SHARED_AREA

3. Use of CALL and ZCALL

"CALL" is a C macro which assures that the simulation works correctly. It should be used in all parts of the operating system; it should be used as a package around ALL routine invocations, just for safety's sake. Instead of calling "foo( arg1, arg2 )", you should instead use "CALL( foo( arg1, arg2 ) )". Use this mechanism faithfully, and the simulation will work fine; screw it up and you will get all kinds of strange bugs. In Releases after 1.2, CALL can cause an interrupt. This more closely simulates the way a real operating system would behave. ZCALL is another C macro. Unlike CALL, it doesn't contain the potential for generating an interrupt. You should use ZCALL whenever you are calling from the Operating System to a hardware interface. So, to reiterate, use CALL around all functions called within your code, and ZCALL when calling the hardware. The code for CALL and ZCALL is in "syscall.h".

4. System Calls

Implemented in Project Phase 1:

4.1 Usage of GET_TIME_OF_DAY

INT32 time_since_midnight;

GET_TIME_OF_DAY( &time_since_midnight );

The value returned is the number of time-units since the most recent midnight.

4.2 Usage of SLEEP

INT32 sleep_time;

SLEEP( sleep_time );

The process becomes "not ready to run" for the number of time units given in "sleep_time".

4.3 Usage of CREATE_PROCESS:

char process_name[N];
void *starting_address;
INT32 initial_priority;
INT32 process_id;
INT32 error;

CREATE_PROCESS( process_name, starting_address, initial_priority, &process_id, &error );

Create a process. This process will have name "process_name", will begin execution at location "starting_address" ( in essence, this is the name of a routine ), and at the start of execution has a priority of "initial_priority". The system call returns the process_id of the created process. An error will be generated if a process with the same name already exists. Lots of other errors are also possible.

Success means error = 0.

4.4 Usage of GET_PROCESS_ID

char process_name[N]
INT32 process_id;
INT32 error;

GET_PROCESS_ID( process_name, &process_id, &error );

Returns the process_id for the process whose name is "process_name". If process_name = "", then return the PID of the calling process. An error is returned if a process with the requested name doesn't exist. Lots of other errors are also possible.

Success means error = 0.

4.5 Usage of TERMINATE_PROCESS

INT32 process_id;
INT32 error;

TERMINATE_PROCESS( process_id, &error );

Terminate the process whose PID is given by "process_id". If process_id = -1, then terminate self. If process_id = -2, then terminate self and any child processes. Termination of a non-existent process results in an error being returned. Upon termination, all information about a process is lost and it will never run again. An error is returned if a process with that PID doesn't exist. Lots of other errors are also possible, for instance, the target process isn't in the hierarchy (isn't a child of) the requester.

Success means error = 0.

4.6 Usage of SUSPEND_PROCESS

INT32 process_id;
INT32 error;

SUSPEND_PROCESS( process_id, &error );

Suspend the process whose PID is given by "process_id". If process_id = -1, then suspend self. Suspension of an already suspended process results in no action being taken. Upon suspension, a process is removed from the ready queue; it will not run again until that process is the target of a RESUME_PROCESS. An error is returned if a process with that PID doesn't exist. Lots of other errors are also possible. Success means error = 0.

4.7 Usage of RESUME_PROCESS

INT32 process_id;
INT32 error;

RESUME_PROCESS( process_id, &error );

Resume the process whose PID is given by "process_id". Resumption of an not-suspended process results in an error. Upon resumption, a process is again placed on the ready queue and is able to run. An error is returned if a process with that target PID doesn't exist. Lots of other errors are also possible.

Success means error = 0.

4.8 Usage of CHANGE_PRIORITY

INT32 process_id;
INT32 new_priority;
INT32 error;

CHANGE_PRIORITY( process_id, new_priority, &error );

Change the priority of the process whose PID is given by "process_id". If process_id = -1, then change self. The result of a change priority takes effect immediately. An error is returned if a process with that target PID doesn't exist. Lots of other errors are also possible.

Success means error = 0.

4.9 Usage of SEND_MESSAGE

INT32 target_pid;
char message_buffer[N];
INT32 message_send_length;
INT32 error;

SEND_MESSAGE( target_pid, message_buffer, message_send_length, &error );

Send a message to the target process. When the target does a "RECEIVE_MESSAGE", place data from this send in the message buffer of that target. message_send_length is the size of the send buffer - it must be larger than or equal in size to the string of data that is actually sent; so this parameter is a buffer size rather than a message size. If the target_pid = -1, then broadcast the message to all potential receivers (although this message will actually be intercepted by only one of those receivers).

Lots of different errors are possible. Success means error = 0.

4.10 Usage of RECEIVE_MESSAGE

INT32 source_pid;
char message_buffer[N];
INT32 message_receive_length;
INT32 message_send_length;
INT32 message_sender_pid;
INT32 error;

RECEIVE_MESSAGE( source_pid, message_buffer, message_receive_length, &message_send_length, &message_sender_pid, &error );

Receive a message from source_pid. If source_pid = -1, then receive from any sender who has specifically targeted you or who has done a broadcast. If -1 is used, then &message_sender_pid contains the pid of the process that did the send. The Operating System will place that message in message buffer if it is less than message_receive_length bytes long. message_receive_length is the size available in the receive buffer. Return the size of the send buffer in message_send_length. In general, a RECEIVE_MESSAGE system call causes the receiver process to suspend itself until the SEND is made. Devise appropriate rules of behaviour for the sender and receiver.

Implemented in Project 2:

4.11 Usage of MEM_READ, MEM_WRITE, and READ_MODIFY

These "calls" are implemented by user programs, but instead of calling the Operating System, they instead call the Z502 hardware directly. They are thus described in Appendix A under the explanation of the hardware interface.

4.12 Usage of DISK_READ

INT16 disk_id;
INT16 cylinder;
INT16 sector;
char data[PGSIZE];

DISK_READ( disk_id, cylinder, sector, &data );

The information stored at <DISK_ID, cylinder, sector> is returned in the variable pointed to by "data". Note that this is a very low level system call. In most Operating Systems, knowledge of the location of physical data is reserved for a filesystem. The call as implemented here assumes that the user program understands the physical disk.

4.13 Usage of DISK_WRITE

INT16 disk_id;
INT16 cylinder;
INT16 sector;
char data[PGSIZE];

DISK_WRITE( disk_id, cylinder, sector, &data );

The information pointed to by data is stored at <DISK_ID, cylinder, sector>. Note that this is a very low level system call. In most Operating Systems, knowledge of the location of physical data is reserved for a filesystem. The call as implemented here assumes that the user program understands the physical disk.

4.14 Usage of DEFINE_SHARED_AREA

INT32 starting_address;
INT32 pages_in_shared_area;
char area_tag[MAX_TAG_LENGTH];
INT32 number_previous_sharers;
INT error;

DEFINE_SHARED_AREA( starting_address, pages_in_shared_area, area_tag, &number_previous_sharers, &error);

The caller specifies the virtual memory address at which the shared area should begin - this address has the same units as do the requests to read and write memory.

The caller also specifies the number of pages that are to be in the shared area. Shared areas must encompass entire pages - you can't split a page with some of it being shared and some of it unshared.

The area_tag is a label used to enumerate which shared area is being asked for. Programs can DEFINE multiple shared areas each having a separate tag. Programs specifying different area_tags will get unrelated shared areas. It would presumably be legal for a process to do two DEFINEs, each with a different starting address, but having the same area_tag, such that the same physical memory would be mapped to two separate virtual locations in the same process.

The number_previous_sharers indicates the number of other processes that currently have this area_tag shared area DEFINED. Thus the Operating System would return a "0" for the first caller, a "1" for the next, and so on.

An error will be returned if for any reason an illegal request is made. There are a host of possible errors that could result from this call.