CSS422 Final Project: Thump-2 Implementation Work of C Standard Library Functions
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
#include
#include
#include
#include
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 ).
2023-02-09