Computer Labs 2012/2013 - 1st Semester
Lab 3: The PC's Timer

1. Objective

The objective of this lab is twofold. First that you learn how to use the PC's timer and speaker. Second that you understand the concept of interrupt, and learn how to use it in the context of this lab.

2. What to Do

Write in C language several functions to use the PC's timer and speaker. The goal is to develop a generic module that will be used to create a library, which you will be able to use in the course's project.

Like in previous labs we specify the prototype of functions that operate on the I/O device. However, for testing/evaluation purposes you will also have to develop a set of functions, known as scaffolding test code, whose prototype we specify. In later labs, we will specify only the testing functions and you will have to design the functions for operating on the I/O device.

Specifically, you shall develop the following test functions:

  1. int timer_test_square(unsigned long rate)
  2. int timer_test_int(unsigned long time)
  3. int speaker_test(unsigned long freq, unsigned long time)

The first two functions are declared in header file timer.h, and file timer.c contains their implementation stubs. The last function is declared in header file speaker.h, and file speaker.c contains its implementation stub. You may find it convenient to add your test code to these files; this way you will avoid mistakes in their definition. Section 6 describes what these functions should do, and specifies the functions that you have to develop to implement them. You can also read the documentation generated by doxygen for the timer and for the speaker modules.

Furthermore, you will also have to develop the function main(), which must be in a file named lab3.c. This will allow us to use our own main() without having to manually editing your files.

To avoid polluting the source tree in /usr/src/drivers/ with SVN metadata, you can create a working copy for this lab in the home directory of user lcom in the Minix 3 VMPlayer image of the lab's PCs. All you need to do is to issue the following commands in lcom's home directory (/home/lcom) in Minix :

$ svn checkout $ cd lcom1213-txgy $ svn mkdir lab3

These commands should be issued as user lcom. You should use root only to install the configuration file lab3 in /etc/system.conf.d/, to install your program in /usr/sbin (actually this is not strictly necessary as described below in Section 7) and to run it with service.

2.1 Class Preparation

So that you can accomplish this lab's objectives by the end of your lab class, you should do some homework. In addition to read, and understand, this handout and the class notes, you should implement timer_test_square() and all the functions that it needs to call, as described in Section 6.1.

3. The PC Timer, the i8254 IC

Every PC "has" an i8254 an IC with 3 timers, whose block diagram is shown in Figure 1. In this lab, you'll use timers 0 and 2. You must not change timer 1 configuration.

Figura 1: The PC timer: i8254.

The 3 timers are identical and operate independently of one another. Thus we will describe only one timer.

A timer has a 16 bit counter, two input lines, Clock and Enable, and an output line Out. The Enable line is used to enable/disable the timer. When a timer is enabled, the value of its counter is decremented by one on every pulse of the Clock line. The value of Out depends on the counter's value and on the operating mode.

3.1 Operating Mode 3: Square wave generator

The i8254 supports several modes, but in this lab you will use only mode 3, square wave generator. In this mode, the Out line is high initially and until the counter reaches half of its initial value. It then goes low until the counter reaches zero, at which time the counter is reloaded with its (pre-programmed) initial value, Out is set to high and the cycle begins again. Thus, in mode 3, the timer generates a square wave with a frequency given by the expression clock/div, where clock is the frequency of the Clock input and div is the value loaded initially in the timer.

3.2 Programming a Timer

Each timer has a 16 bit counter, which may be both read and written. Furthermore, the i8254 has a single control register that can be written only, and that is used to configure the operation of all timers.

Each timer is programmed independently of the other timers. Programming a timer requires two steps:

  1. Specifying the timer operating mode, by writing a control word to the control register
  2. Loading the counter initial value, by writing to the counter register.

The format of the control word (a 8-bit value) is shown in Table 1.

Bit Value Function Bit Value Function
7,6 Select counter 3,2,1 Operating Mode
00 0 000 0
01 1 001 1
10 2 x10 2
11 Read-Back Command x11 3
5,4 Type of Access 100 4
01 LSB 101 5
10 MSB 0 Counting mode
11 LSB followed by MSB 0 Binary (16 bits)
1 BCD (4 decades)
Table 1: Format of the i8254 control word (byte).

Thus, bits 6 and 7 specify which timer to program. Bits 1, 2 and 3 specify the operating mode. Bit 0 specifies whether the counter is a binary or a BCD counter, i.e. whether the inital value should be interpreted as a binary or a BCD value. Bits 4 and 5 specify how the initial value is loaded. The following paragraph provides some more details regarding these bits.

Although the counters are 16 bits, the i8254 has only 8 data lines. Thus to load the initial value of a counter, the LSB and the MSB must be written separately. The i8254 allows loading either of these bytes, or both of them. In the latter case, the LSB must be loaded first. Which bytes of the counter will be loaded in the second step is specified by bits 4 and 5.

3.3 Use of the i8254 on the PC

The PC uses each timer for a different purpose. Nevertheless, all timers use the same clock signal with frequency 1193181 Hz.

Timer 2

As shown in Figure 1, the output of timer 2 is connected to the PC speaker, and is used to generate tones by generating a square wave of an audible frequency. For example, to generate a 1000 Hz tone, the timer must be programmed to operate in mode 3, with an initial value of 1193.

Furthermore, to enable the speaker, you must set to 1 both bits 0 and 1 of I/O port 0x61 (SPEAKER_CTRL) . As shown in Figure 1, bit 0 is connected to the GATE of Timer 2 and if low, it will disable the timer. In addition, the OUT line of timer 2 is not connected directly to the speaker, instead it is gated via a NAND, whose other input line comes from bit 1 of port 0x61. Thus, unless that bit is set to 1, the input to the speaker will always be high, and no tone will be generated.

Timer 0

Figure 1 also shows that the output of the timer 0 is directly connected to line IRQ0 of the PC's interrupt controller. It is usually programmed in mode 2, to generate a more or less stable time reference that can be used by the operating system to measure time with a resolution of a few milliseconds. In Section 4 we describe the interrupt mechanism used by the PC, and in Section 5 we describe the use in Minix 3 of the interrupts generated by the timer 0.


The registers of the i8254, like those of most other PC's I/O devices, are mapped in the I/O address space of the PC's processor. The address of the control register (TIMER_CTRL) is 0x43, the address of timer 0 is 0x40 (TIMER_0) and the address of timer 2 is 0x42 (TIMER_2). Although the counters of these timers are 16 bit, all these registers are 8 bits, and access to the MSB and the LSB of each counter is done as described above in Section 3.2.

4. PC's Interrupt HW

Note: This section describes the PC's priority interrupt handling based on the i8259. Although current systems support a more advanced mechanism (the APIC), it is still possible to use the older interface.

In the PC, all HW interrupts are processed using the i8259 IC, the priority interrupt controller (PIC). This IC has 8 interrupt request (IRQ) lines which are connected directly to I/O devices. These lines have an implicit priority: IRQ line 0 has the highest priority, next comes IRQ line 1, and so on until IRQ line 7. This means, that the PIC will forward an interrupt request to the processor, only if no interrupt with the same or higher priority is being processed. Furthermore, it is possible to mask each line independently: while an IRQ line is masked, the PIC will not forward any interrupt request on that line to the CPU.

The PC uses two PICs in cascade, as shown in Figure 2, with the INT line of the second one (the slave) connected to the IRQ line 2 of the first one (the master). This means that IRQ lines 0 and 1 of the master have higher priority than the IRQ lines of the slave PIC. However, all IRQ lines of the slave have higher priority than IRQ lines 3 to 7 of the master.

Figure 2: The PC HW interrupt mechanisms.

Figure 2 outlines the interrupt mechanism used in the PC. When an I/O device activates its interrupt request line, the PIC will activate the CPU's interrupt line, initiating an interrupt sequence. The CPU will then save the address of the instruction being executed and the flags register on the stack and will disable interrupts, and will respond by activating an interrupt acknowledgment line. When the PIC detects that this line is active, it will put an 8-bit value, which was previously programmed on the PIC, in the data bus. The processor then uses this 8-bit value as an index to a table (the Interrupt Descriptor Table) whose entries contain the addresses of interrupt service routines. The processor will then transfer control to the address of the entry corresponding to the 8-bit value received from the PIC. As a result, the processor will execute the device's interrupt service routine, or interrupt handler, which is responsible for informing the PIC that it has "finished" handling the interrupt, and must terminate with instruction IRETD.

The sequence described in the previous paragraph, assumes that:

  1. the interrupt request line is not masked on the PIC, and no interrupt with higher or equal priority is being processed, otherwise the PIC will postpone the execution of the interrupt sequence;
  2. the interrupts are enabled on the processor, otherwise the processor will postpone execution of the interrupt sequence.

Because the PIC does not generate another interrupt for the same device or for another with lower priority until it is informed that the current interrupt has already been handled, it is up to the interrupt handler (IH) to do it, by writing the EOI command (0x20) to the control register. If the interrupt originates on the slave PIC, the IH will need to issue the EOI command to both the slave and the master PICs. Table 4 shows the addresses of the PIC registers and Table 5 shows the IRQ lines and the interrupt vectors for common I/O devices of a PC.

PIC PIC Controller Register Interrupt Mask Register
PIC1 0x20 0x21
PIC2 0xA0 0xA1
Table 4: PIC I/O ports.
PIC 1 Device Vector PIC 2 Device Vector
IRQ0 Timer 0 0x08 IRQ8 Real Time Clock 0x70
IRQ1 Keyboard 0x09 IRQ9 Replace IRQ2 0x71
IRQ2 slave 8259 0x0A IRQ10 Reserved 0x72
IRQ3 Serial device COM2 0x0B IRQ11 Reserved 0x73
IRQ4 Serial device COM1 0x0C IRQ12 Mouse 0x74
IRQ5 Reserved/Sound card 0x0D IRQ13 Math coprocessor 0x75
IRQ6 Diskette 0x0E IRQ14 Hard disk 0x76
IRQ7 Parallel port 0x0F IRQ15 Reserved 0x77
Table 5: PC's I/O Devices IRQ lines and interrupt vectors.

An interrupt handler cannot take any arguments nor return any values. Furthermore, it must save all the registers that it uses and must terminate with the IRETD instruction, which resets the stack and the processor to its state at the time the interrupt occurred. Because the interrupts are disabled while the interrupt handler executes, the CPU will not be handle further interrupts, therefore an interrupt handler should be as short as possible; if necessary, the interrupt handler may enable interrupts by executing the STI instruction.

5 Minix 3 Notes

5.1 I/O Ports Access

In this lab, you'll use only C language. Because the C language does not provide any operators or standard functions that allow access to I/O ports, you'll have to use functions provided by Minix 3 instead.

Direct I/O port access is a very powerful capability that can be easily misused and that can interfere with the proper operation of the operating system and other processes. Thus, in Minix 3, I/O port access is a privileged operation, and is provided via the SYS_DEVIO kernel call, and several libsys.a functions, that hide the details of making a kernel call from the user-level programmer. For this lab, you may find useful the following functions:

#include <minix/syslib.h> int sys_inb(port_t port, unsigned long *byte); int sys_outb(port_t port, unsigned long byte);

As usual, this requires adding a permission to execute this kernel call (SY_DEVIO) to the file lab3 that you'll have to add to the /etc/system.conf.d/ directory. Furthermore, that file should also specify the I/O ports that the process is allowed to access via this call.

5.2 Interrupt Handling

Interrupt handling with Minix 3 is somewhat unusual, because device drivers are user level processes. Indeed, to ensure that the interrupt handler of a device driver does not mess with the kernel, interrupt servicing is also done at the user level by the device driver.

To allow this, the Minix 3 (micro) kernel has a generic interrupt handler, which notifies a device driver when an interrupt it may have to service occurs. Although this might appear strange at first, the truth is that an interrupt handler does not take any arguments and does not return any value. Thus, a simple notification is all that is required from the kernel. Handling of the interrupt proper must be done by the device driver.

The major disadvantage of this approach is that the interrupt servicing latency may become too large for devices that are very fast, such as gigabit Ethernet cards.

Subscribing an Interrupt Notification

To support this model, Minix 3 provides also the SYS_IRQCTL kernel call, and several libsys.a functions, that hide the details of making a kernel call from the user-level programmer. For this lab, you may find useful the following functions:

#include <minix/syslib.h> int sys_irqsetpolicy(int irq_line, int policy, int *hook_id); int sys_irqrmpolicy(int *hook_id); int sys_irqenable(int *hook_id); int sys_irqdisable(int *hook_id);

All these calls return OK on success and 3 other values on failure.

sys_irqsetpolicy(int irq_line, int policy, int *hook_id)
This function should be used to subscribe a notification on every interrupt in the input irq_line. The policy argument specifies whether or not the interrupt on that IRQ line should be automatically enabled by the generic interrupt handler, or whether the device driver interrupt handler will do it. Finally, the hook_id argument is used both for input to the call and output from the call. The caller should initialize it with a value that will be used in the interrupt notifications, as described below. The value returned by the call in this argument must be used in the other calls to specify the interrupt notification to operate on.
int sys_irqrmpolicy(int *hook_id);
This function unsubscribes a previous subscription of the interrupt notification associated with the specified hook_id
int sys_irqenable(int *hook_id)
This function enables interrupts on the IRQ line associated with the specified hook_id. This may be convenient if the policy specified in the sys_irqsetpolicy() call does not enable interrupts automatically.
int sys_irqdisable(int *hook_id)
This function disables interrupts on the IRQ line associated with the specified hook_id.

Again, subscribing and unsubscribing interrupt notifications, and enabling and disabling interrupts are privileged operations. Therefore, the execution of these operations, and the IRQ lines on which they are allowed to operate must be specified in a file /etc/system.conf.d/ for your process.

Receiving an Interrupt Notification

The Minix 3 generic interrupt handler uses the Minix 3 interprocess communication (IPC) mechanism to notify a subscriber of the occurrence of an interrupt. This IPC mechanism is essentially a mechanism for sending and receiving messages between processes. Interrupt notifications are a special kind of message supported by Minix 3 IPC.

As a consequence, in Minix 3 a device driver is an event driven service that receives and processes messages, either interrupt notifications from the kernel, or service requests (usually I/O operations) from other processes.

The program that you will have to develop in this lab, is not a standard device driver, in that it does not receive service requests from other processes, but only interrupt notifications from the kernel. Thus, your program should include loop in which interrupt notifications are received and handled. In a standard Minix 3 device driver, this loop is endless. In this lab, you may want to terminate the loop after a few iterations, or on some event. The following code segment illustrates the general structure of the main loop and the Minix 3 functions that should be used.

1: #include <minix/drivers.h> 2: #include <minix/com.h> 3: 4: int ipc_status; 5: message msg; 6: 7: while( 1 ) { /* You may want to use a different condition */ 8: /* Get a request message. */ 9: if ( driver_receive(ANY, &msg, &ipc_status) != 0 ) { 10: printf("driver_receive failed with: %d", r); 11: continue; 12: } 13: if (is_ipc_notify(ipc_status)) { /* received notification */ 14: switch (_ENDPOINT_P(msg.m_source)) { 15: case HARDWARE: /* hardware interrupt notification */ 16: if (msg.NOTIFY_ARG & irq_set) { /* subscribed interrupt */ 17: ... /* process it */ 18: } 19: break; 20: default: 21: break; /* no other notifications expected: do nothing */ 22: } 23: } else { /* received a standard message, not a notification */ 24: /* no standard messages expected: do nothing */ 25: } 26: }

Function driver_receive() in line 9, is a function provided by the libdrivers.a library. It should be used by device drivers to receive messages, including notifications, from the kernel or from other processes. The first argument specifies the sender of the messages we want to receive. The value ANY means that the driver accepts messages from any process. The second and third arguments are the addresses of variables of type message and int, which will be initialized, by the driver_receive() code, with the message received and IPC related status, respectively.

The macro is_ipc_notify() in line 13, returns true if the message received is a notification or false otherwise, i.e. if it is a standard message.

The member m_source of type message, used in line 14, contains the endpoint of the sender of the message. The endpoint is an address used by Minix 3 IPC to specify the communication endpoints, i.e. the source and destination of a communication instance. The macro _ENDPOINT_P allows to extract the process identifier from a process's endpoint. The value HARDWARE, used in line 15, is a special process identifier value to indicate a HW interrupt notification. The reason for the use of a process identifier different from the endpoint is that the endpoint of a process may change in time, just like an address, but most of the time we are not interested in the address, but rather on the entity behind that address. Now, you may ask: if so, why use endpoints? Good question, but this is "out of scope" of this course.

Finally, the NOTIFY_ARG macro in line 16 is a reader friendly name for the member of a message type that contains the "argument" of an interrupt notification. This is a 32-bit bitmask with the bits of the active interrupts subscribed set to 1. The bit of an interrupt notification is the one passed in the hook_id argument of the sys_irqsetpolicy() call. For example, if the value used for one interrupt was 2, then when that interrupt occurs, the generic interrupt handler will send a notification to the device driver with bit 2 of the NOTIFY_ARG member of a message set to 1. This scheme allows the kernel to use a single notification message to notify a process of the occurrence of several interrupts on different IRQ lines.

In this lab, as in all other labs, you need only to handle one interrupt. This is because usually a device only uses one interrupt line. Thus, the code in line 17, will just handle the interrupt, or better call the interrupt handler.

However, in your project, the program you'll develop will use more than one device and therefore will have to handle more than one interrupt. Thus, variable irq_set should be a bitmask with the bits corresponding to the hook_id values used in the sys_irqsetpolicy() set to 1. The code in line 17, should then identify which of these bits are set and call the corresponding handler.

6. Testing Code

As stated above, one of the goals of this lab is to develop a generic module that will be used to create a library, which you will be able to use in the course's project. We could certainly specify the API, as we have done for the previous labs. (Actually, we will also do it for this lab.) But we want you to play a more active role. Rather than being just an implementer of an API, we want you to design an API.

We'll use this lab to show you by example how you can develop a fairly general API, which can be used to operate on the timer/counter, and that you can use to implement the testing functions that we will use to test your code. In the next labs, we'll just specify the testing functions, as we have done above, and it will be up to you to design the API for operating on the I/O devices.

6.1 timer_test_square()

The purpose of this function is to test your code to configure a timer to generate a square wave with a given frequency.

As mentioned above, Minix 3 uses Timer 0 as a time reference to maintain the time of the day and for implementing SW timers (for measuring the duration of time intervals). Thus, when it starts up, it configures Timer 0 to generate interrupts at a fixed rate, by default 60 Hz. Minix 3 then uses a counter that it increments on every Timer 0 interrupt, and uses its value to measure the time -- it expects that counter to be incremented by 60 every second.

timer_test_square() should configure Timer 0 to generate a square wave with a frequency equal to its argument. Thus, if, for example, it is invoked with an argument of 30 (meaning 30 Hz), Timer 0 counter will be incremented by 30 every second rather than by 60. As a result it will rapidly become late, which you'll be able to check by giving the command date

$ date

and by comparing with the time reported by Linux.

You could easily implement this function by loading the appropriate value into Timer 0 counter. However, this would hardly be reusable (except by cut and pasting). It would not allow us to control, for example, other timers. What we want is that you implement a slightly more general function that is able to configure a timer to generate a square wave with a given frequency:

int timer_set_square(unsigned char timer, unsigned long freq)

This function can then be reused for example to configure the frequency of the tone to be generated by the speaker. Function timer_test_square() would need only to call timer_set_square() with the appropriate arguments. Function timer_set_square() should also be implemented in timer.c. (You may wish to use the file timer.c, which includes stubs for this function.) For more information about it please read its doxygen documentation.

6.2 timer_test_int()

The purpose of this function is to test your code that handles interrupts generated by Timer 0. With that in mind, it should print a message on every second, using the function printf(), for a time interval whose duration is specified in its argument. These messages will appear on the console and will be appended to the file /usr/log/messages. Thus, if you are on a terminal different from the console, you can still see these messages by giving the command:

$ tail -f /usr/log/messages

Again, you could implement the entire functionality required-- subscribing an interrupt, handling the interrupts and unsubscribing an interrupt -- inside timer_test_int(), by invoking the right Minix 3 kernel calls. Again, that would not be reusable. More interesting is to design functions that you can use later in the project:

void timer_int_handler() int timer_subscribe_int() int timer_unsubscribe_int()

which should be implemented in a file named timer.c. (You may wish to use the file timer.c, which includes stubs for these functions.) The following paragraphs describe these functions. You can also read their doxygen documentation.

As stated above the main use for the periodic interrupt generated by Timer 0 is as a time reference that can be used to measure time. This requires incrementing a counter on every interrupt. Thus all timer_int_handler() needs to do is to increment a global counter variable.

The implementation of timer_subscribe_int() is straightforward. You must call the sys_irqsetpolicy() and the sys_irqenable() calls, described in the paragraph on interrupt subscription in Subsection 5.2 . The policy you should specify in sys_irqsetpolicy() is IRQ_REENABLE, so that the generic interrupt handler will acknowledge the interrupt, i.e. ouput the EOI command to the PIC, thus enabling further interrupts on the corresponding IRQ line. The implementation of timer_unsusbscribe_int() is even simpler.

Although these functions can be reused to (un)subscribe Timer 0 interrupts, they cannot be used to (un)subscribe interrupts for other I/O devices. Although, we would like to be able to do that, it is a bit too early to devise a general API. It requires more experience with the use of interrupts on Minix 3, as well as the use of some C features that you do not know yet.

This is even "more true", for the "interrupt loop", i.e. the loop for receiving an interrupt notification. Thus, right now I suggest that the interrupt loop be included in timer_test_int().

6.3 speaker_test()

The purpose of this function is to test your code that controls the PC speaker. With that in mind, it should configure the speaker to generate a tone, i.e. a sound with a fixed frequency, for a specified time interval.

Configuring the speaker to generate a tone is fairly straightforward. First, the code must configure Timer 2 to generate a square wave of a fixed frequency. Then, the code must enable the output of Timer 2 to the speaker for the specified time interval. How to do this is described in the paragraph on Timer 2 in Subsection 3.3.

To program Timer 2, your code can invoke function timer_set_square(), which was already described above in Section 6.1. In order to control the output you should implement function:

speaker_ctrl(unsigned char on)

in a file named speaker.c. (You may wish to use the file speaker.c, which includes stubs for these functions.)

As described in its doxygen documentation, this function should set/reset the appropriate bits on port 0x61 to enable/disable the speaker. One alternative would be to provide a function that would allow to individually control each of the bits. However, this is likely to be a useless generalization, because we'll be using port 0x61 only to control the speaker.

As for measuring the time interval, if you were able to complete timer_test_int(), you should use Timer 0 interrupts. Otherwise, you can use the function:


which suspends the execution of the process for the number of seconds specified in its argument.

Because VMware Player does not emulate the PC speaker, testing of this part of the lab, must be done on the Minix 3 image in the hard disk (HD) partition.

I suggest that you develop your code on Linux using Eclipse's RSE plugin. Once it compiles and runs in VMware Player you can try it on the PC. For that, you'll need to move your code to the Minix 3 partition on the HD. Possible ways to do it are described in Annex 1.

If your program does not run as expected on the PC you can try to fix it using one the available editors (if you have never used vi I suggest you use nano). Compile and run it as usual. Although, you can go back to Linux and use Eclipse to fix your program, compile it and run it before testing it on the PC, the development cycle will be much slower.

Remember that:

  1. In the lab's computers, when you login as user lcom in Linux, the entire home directory of that user is reinitialized. Therefore, if you reboot to the Minix 3 image on the hard disk and later login to Linux, all the files that you had in Linux will be lost. So, please commit all your work to the SVN repository before rebooting the PC. (Or, if your SVN repository is misconfigured, save your code somewhere else, for example your storage area in FEUP.)

7. Compiling and Installing your Program

Compilation, installation and execution of your program can be performed by issuing the same commands you used in the previous labs, this time in the /home/lcom/lcomt1213-txgy/lab3 directory you should have created at the beginning of this lab.

Actually, you need not install your program in /usr/sbin/. You can use the executable in /home/lcom/lcom1213-txgy/lab3/ as argument of service. For example, assuming that you are in this directory you can issue the following command as root:

# service run `pwd`/lab3

The `pwd` expression evaluates to the output of the command pwd, which returns the present working directory (/home/lcom/lcom1213-txgy/lab3, according to our assumptions). This is simpler than having to type the full path:

# service run /home/lcom/lcom1213-txgy/lab3

8. Configuring your Program

Your program invokes functions that are privileged. Thus, before you can run it, you need to add a file named lab3 to the /etc/system.conf.d/ directory to grant your program the necessary permissions.

Unless you use privileged functions that are not really necessary, the following entry is enough:

service lab3 { system DEVIO IRQCTL ; ipc SYSTEM rs vm pm vfs ; io 40:4 # i8254 ports 61 # port for speaker control ; irq 0 # TIMER 0 IRQ ; uid 0 ; };

9. Submission

You should submit both the code you prepared for the class and the final code via the SVN repository of the project you created on Redmine. This code should be under directory lab3, which should be at the top level of the SVN repository of your Redmine project.

Annex 1: Transferring your code to the Minix partition on the HD

Thanks to the dedication and hard work of the lab's technician, Rui Fernandes, you can now use the network to transfer your code to the Minix partition on the lab's PCs.

You can transfer your code via the network, by using rsync in a way similar to that describe in Lab 2's Annex 2. Now, however, you must first upload your files to a computer with an rsync server, such as, or For example, let's assume that your code for lab3 is in /home/lcom/lab3 on Minix in the VMwarePlayer. To upload your code to your personal area, assuming that your current working directory is /home/lcom/, you can use the command:

$ rsync -auv lab3 <your_login_name>

Upon request, introduce your FEUP password, and if valid, the entire lab3 directory should be transferred to a new directory named lab3 under the top level directory in your storage area in FEUP (assuming, that no such directory existed).

After booting Minix on the PC, you can download the files you have previously uploaded. Just login as lcom and after that issue the command:

$ rsync -auv <your_login_name> ./

Again, you'll be prompted for your password, after entering it, rsync will create a directory named lab3 under the directory where you run rsync (should be /home/lcom, if you followed the instructions).

If using the network is not a choice, because e.g your PC's network card is not supported by Minix, you'll need to use a floppy to move the code from Linux to Minix. In Linux, to copy a file from the HD to a floppy, and vice-versa, you need to use the utility mcopy. For example, to copy file timer.c to the floppy, you can issue the following command:

$ mcopy timer.c a:

In Minix, to copy from the floppy to the HD you need to use the command dosread. For example, to copy file timer.c from the floppy to your current directory, you can issue the following command:

$ dosread a timer.c > timer.c

If later you wish to make a copy the code you have changed in Minix on the HD, you can use the Minix command doswrite.

$ doswrite a timer.c < timer.c

For more information on how to use these commands, please read their man pages.


This lab is based on a lab by João Cardoso and Miguel P. Monteiro for DJGPP running on Windows98.