Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit

CSS422 Final Project

Thumb-2 Implementation Work of Memory/Time-Related C Standard Library Functions.

1. Objective

You’ll understand the following concepts at the ARM assembly language level through this final project that implements memory/time-related C standard library functions in Thumb-2.

· CPU operating modes: user and supervisor modes

· System-call and interrupt handling procedures

· C to assembler argument passing (APCS: ARM Procedure Call Standard)

· Stack operations to implement recursions at the assembly language level

· Buddy memory allocation

This document is quite dense. Please read it as soon as the final project spec. becomes available to you.

2. Project Overview

Using the Thumb-2 assembly language, you will implement several functions of the C standard library that will be invoked from a C program named driver.c. See Table 1. These functions must be code in the Thumb-2 assembly language. Some of them can be implemented in stdlib.s running in the unprivileged thread mode (=user mode), whereas the others need to be implemented as supervisor calls, (i.e., in the handler mode = supervisor mode). For more details, log in one of the CSS Linux servers and type from the Linux shell:

man 3 function where function is either bezro, strncpy, malloc, free, signal, or alarm

Table 1: C standard lib functions to be implemented in the final project

C standard lib functions

In stdlib.s *1

SVC *2

bzero( void *s, size_t n )

writes n zeroed bytes to the setring s. If n is zero, bzero( ) does nothing.

Yes

strncpy(char *dst, const char *src, size_t len)

copies at most len characters from src into dst. It returns dst.

Yes

malloc( size_t size )

allocates size bytes of memory and returns a pointer to the allocated memory. If successful, it returns a pointer to allocated memory. Otherwise, it returns a NULL pointer.

Yes

free( void *ptr )

Deallocates the memory allocation pointed to by ptr. If ptr is a NULL pointer, no operation is performed. If successful, it returns a pointer to allocated memory. Otherwise, it returns a NULL pointer.

Yes

void (*signal( int sig, void (*func)(int))))(int);

Invokes the func procedure upon receipt of a signal. Our implementation focuses only on SIGALRM, (whose system call number is 14.)

Yes

unsigned alarm( unsigned seconds )

sets a timer to deliver the signal SIGALRM to the calling process after the specified number of seconds. It returns the amount of time left on the timer from a previous call to alarm( ). If no alarm is currently set, the return value is 0.

Yes

*1: To be implemented in stdlib.s in the unprivileged thread mode

*2: To be passed as an SVC to SVC_Hander in the privileged handler mode

The driver.c we use is shown in listing 1. It tests all the above six stdlib functions. Please note that printf() in the code will be removed when you test your assembly implementation, because we won’t implement the printf( ) standard function.

Listing 1: driver.c program to test your implementation

#include  // bzero, strncpy

#include   // malloc, free

#include   // signal

#include   // alarm

#include    // printf

int* alarmed;

void sig_handler1( int signum ) {

*alarmed = 2;

}

void sig_handler2( int signum ) {

*alarmed = 3;

}

int main( ) {

char stringA[40] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabc\0";

char stringB[40];

bzero( stringB, 40 );

strncpy( stringB, stringA, 40 );

bzero( stringA, 40 );

printf( "%s\n", stringA );

printf( "%s\n", stringB );

void* mem1 = malloc( 1024 );

void* mem2 = malloc( 1024 );

void* mem3 = malloc( 8192 );

void* mem4 = malloc( 4096 );

void* mem5 = malloc( 512 );

void* mem6 = malloc( 1024 );

void* mem7 = malloc( 512 );

free( mem6 );

free( mem5 );

free( mem1 );

free( mem7 );

free( mem2 );

void* mem8 = malloc( 4096 );

free( mem4 );

free( mem3 );

free( mem8 );

alarmed = (int *)malloc( 4 );

*alarmed = 1;

printf( "%d\n", *alarmed);

signal( SIGALRM, sig_handler1 );

alarm( 2 );

while ( *alarmed != 2 ) {

void* mem9 = malloc( 4 );

free( mem9 );

}

printf( "%d\n", *alarmed);

signal( SIGALRM, sig_handler2 );

alarm( 3 );

while ( *alarmed != 3 ) {

void* mem9 = malloc( 4 );

free( mem9 );

}

printf( "%d\n", *alarmed);

return 0;

}

This driver program repeats allocating and deallocating memory space and thereafter sets the sig_handler1( ) function to be called upon receiving the first timer interrupt (in 2 seconds) and sig_ahndler2( ) function to be called upon the second timer interrupt (in 3 seconds).

3. System Overview and Execution Sequence

3.1. Memory overview

This project maps all code to 0x0000.0000 – 0x1FFF.FFFF in the ARM’s usual ROM space (as the Keil C compiler/ARM assembler does) and defines a heap space; user and SVC stacks; memory control block (MCB) to manage the heap space; and all the SVC-related parameters over 0x2000.1000 – 0x2000.7FFF in the ARM’s usual SRAM space. See table 2.

Table 2: Memory overview

Address

Size (hex)

Size (B)

Usage

0x400F.E600 – 0x400F.F028

0x0000.0A28

2.6KB

uDMA registers (memory mapped IO)

0x2000.7C00 – 0x2000.7FFF

0x0000.0400

1KB

uDMA memory map (ch 30)

0x2000.7B80 – 0x2000.7BFF

0x0000.0080

128B

System variables used by timer.s

0x2000.7B00 – 0x2000.7B7F

0x0000.0080

128B

System call table used by svc.s

0x2000.6C00 – 0x2000.7AFF

0x0000.0F00

3.8KB

Not used for now

0x2000.6800 – 0x2000.6BFF

0x0000.0400

1KB

Memory control block to manage in heap.s

0x2000.6000 – 0x2000.67FF

0x0000.0800

2KB

Not used for now.

0x2000.5800 – 0x2000.5FFF

0x0000.0800

2KB

SVC (handler) stack: used by all the others

0x2000.5000 – 0x2000.57FF

0x0000.0800

2KB

User (thread) stack: used by driver.c stdlib.s

0x2000.1000 – 0x2000.4FFF

0x0000.4000

16KB

Heap space controlled by malloc/free

0x2000.0000 – 0x2000.0FFF

0x0000.1000

4KB

Keil C compiler-reserved global data

0x0000.0000 – 0x1FFF.FFFF

0x2000.0000

512MB

ROM Space: all code mapped to this space

Since we compile driver.c together with our assembly programs, the Keil C compiler automatically reserves driver.c-related global data to some space within 0x2000.0000 – 0x2000.0FFF, which makes it difficult for us to start Master Stack Pointer (MSP) exactly at 0x2000.6000 toward to the lower address as well as to start Process Stack Pointer (PSP) at 0x2000.5800. So, it’s sufficient to map MSP and PSP around 0x2000.6000 and 0x2000.5800 respectively. For the purpose of this memory allocation, you should declare the space as shown in listing 2:

Listing 2: The memory space definition in Thumb-2

Heap_Size EQU     0x00005000

AREA    HEAP, NOINIT, READWRITE, ALIGN=3

__heap_base

Heap_Mem SPACE   Heap_Size

__heap_limit

Handler_Stack_Size EQU 0x00000800

Thread_Stack_Size EQU 0x00000800

AREA STACK, NOINIT, READWRITE, ALIGN=3

Thread_Stack_Mem SPACE Thread_Stack_Size

__initial_user_sp

Handler_Stack_Mem SPACE Handler_Stack_Size

__initial_sp

3.2. Initialization, system call, and interrupt sequences

(1) Initialization: the ARM processor reads the first 8 bytes to set MSP and the next 8 bytes to jump to the Reset_Handler routine (as you studied in the class). You don’t have to change the original vector table. Reset_Handler initializes all the data structures you’ve developed and finally calls __main with listing 3.

Listing 3: The last two instructions in Reset_Handler (startup_TM4C129.s)

LDR     R0, =__main

BX      R0

These last two statements are from the original startup_TM4C129.s. Then, the main( ) function in driver.c is invoked.

(2) System calls: whenever main( ) calls any of stdlib functions including bzero, strncpy, malloc, free, signal, and alarm, the control needs to move to strlib.s. In other words, you need to define these function protocols in strlib.s, as shown in listing 4:

Listing 4: The framework of stdlib.s

AREA |.text|, CODE, READONLY, ALIGN=2

THUMB

EXPORT _bzero

_bzero

; Implement the body of bzero( )

MOV pc, lr ; Return to main( )

EXPORT _strncpy

_strncpy

; Implement the body of strncpy( )

MOV pc, lr ; Return to main( )

EXPORT _malloc

_malloc

; Invoke the SVC_Handler routine in startup_TM4C129.s

MOV pc, lr ; Return to main( )

EXPORT _free

_free

; Invoke the SVC_Handler routine in startup_TM4C129.s

MOV pc, lr ; Return to main( )

EXPORT _signal

_signal

; Invoke the SVC_Handler routine in startup_TM4C129.s

MOV pc, lr ; Return to main( )

EXPORT _alarm

_alarm

; Invoke the SVC_Handler routine in startup_TM4C129.s

MOV pc, lr ; Return to main( )

END

Among these six stdlib functions, you’ll implement the entire logic of bzero( ) and strncpy( ) as they may be executed in the user mode. However, the other four functions must be handled as a system call. You need to invoke SVC_Handler in startup_TM4C129.s. Based on the Linux system call convention, use R7 to maintain the system call number. Arguments to a system call should follow ARM Procedure Call Standard, as summarized in table 3.

Table 3: System Call Parameters

System Call Name

R7

R0

R1

alarm

1

arg0: seconds

signal

2

arg0: sig

arg1: func

malloc

3

arg0: size

free

4

arg0: ptr

SVC_Handler must invoke _systemcall_table_jump in svc.s. This in turn means you must prepare the svc.s file to implement _systemcall_table_jump. This function initializes the system call table in _systemcall_table_init as shown in Table 4:

Table 4: System Call Jump Table

Memory address

System Calls

Jump destination

0x2000.7B10

#4: free( )

_kfree in heap.s

0x2000.7B0C

#3: malloc( )

_kalloc in heap.s

0x2000.7B08

#2: signal( )

_signal_handler in timer.s

0x2000.7B04

#1: alarm( )

_timer_start in timer.s

0x2000.7B00

#0

Reserved

Each table entry records the routine to jump. For this purpose, svc.s needs to import the addresses of these routines, using the code snippet shown in listing 5:

Listing 5: Entry points to kernel functions imported in svc.s

IMPORT _kfree

IMPORT _kalloc

IMPORT _signal_handler

IMPORT _timer_start

When called from SVC_Handler, _system_call_table_jump checks R7, (i.e., the system call#) and refers to the corresponding jump table entry, and invokes the actual routine. The merit of using svc.c is to minimize your modifications onto startup_TM4C129.s.

(3) Interrupts: This final project only handles SysTick interrupts. The SysTick timer gets started with _timer_start that was invoked when main( ) calls alarm( ). Note that SysTick timer can count down up to 1 second. Therefore, if main( ) calls alarm( 2 ) or alarm( 3 ), you’ll get a SysTick interrupts at least twice or three times. Upon receiving a SysTick interrupt, the control jumps to SysTick_Handler in startup_TM4C129.s. The handler routine will invoke _timer_update in timer.s to decrement the count provided by alarm( ), to check if the count reached 0, and if so to stop the timer as well as invoke func specified by signal( SIG_ALRM, func ).