CA644, System Software
Table of Contents
CA644, System Software
Dr. Niall McMahon
2022-10-11
If you print these slides, think about using two pages per sheet although don't worry too much about it!
Dr. Niall McMahon
Drawing on previous work by:
Dr. Michael Scriney
Dr. Long Cheng
And sources credited in the references.
Autumn 2022.
.c
extension, i.e. hello_world.c
.
gcc
(GNU Compiler Collection) is used to compile the program so that it can be run.
.o
. The object file is used to build the executable files.
gcc hello_world.c -o hello_world.o
.
-o
flag creates the output executable that can be run using the command ./hello_world
.
-Wall
flag, i.e. gcc -Wall hello_world.c -o hello_world.o
, enables (most) compiler warnings.
gdb
.
gdb hello_world.o
.
gdb
.
gcc -E hello_world.c > hello_world.i
. The -E
flag explicitly runs the preprocessor.
gcc -S hello_world.i
. The -S
flag explicitly creates assembly code output, .s
file.
gcc -c hello_world.s
. The -c
flag explicitly creates an object file, .o
file. This is machine code.
gcc -o hello_world hello_world.c
. The -o
flag explicitly creates the executable by finding the missing functions, for example, printf()
and scanf()
from the standard C library, libc
.
gcc -o hello_world hello_world.c
.
/* Hello world! */
/* This is a comment
that spans
multiple lines. */
//This is a single line comment.
//Comments, as usual, are ignored by the compiler.
#include <stdio.h>
/*stdio.h is a header file that contains input and output functions for files.
int main()
{
printf("Hello World!\n");
return 0; //Return 0 means success.
}
Commonly used header files include:
stdlib.h
: defines four variable types, macros and functions. General purpose.
stdio.h
: standard input and output definitions, macros and functions.
string.h
: type definition, one macro and functions for manipulating character arrays.
time.h
: time type definitions, macros and functions.
ctype.h
: functions useful for testing and mapping characters, i.e. deciding which class a character falls into - if it is an alphabetic character, a control character etc.
math.h
: math functions and one macro.
Variables are declared before use and types include:
int
: basic integer type (16 bit minimum size).
float
: single precision floating point type (32 bit minimum size).
char
: this is the smallest addressable unit of the machine that can contain a basic character set. It is an integer type (8 bit minimum size).
And others.
/* Calculate the area of a rectangle */
#include <stdio.h>
int main()
{
float height = 5.0;
float width = 15.0;
float area = height*width;
printf("Area is: %f\n", area);
}
The specifier "%f"
casts area
as a float.
getchar()
is used to read a character from the command line.
011 0000
.
If we wish to read in a number then the following code would be used:
int fromInput = getchar();
int myInt = fromInput-48;
To print out the numbers 1 - 5, use:
int i = 0;
for(i; i < 6; i++)
{
printf("%d",i);
}
The specifier "%d"
casts i
as a decimal integer.
Create an integer variable myint
and assign it the value 5
.
int myint = 5;
The next line returns the address in memory where
printf("%d", &myint);
We can assign this address to a pointer. The declaration int *
creates a pointer to an integer value.
int *newpointer;
The next line assigns the address of the variable a to the pointer variable newpointer
.
newpointer = &myint;
Printing to screen newpointer
will give the same result as printing to screen the address of myint
, i.e. &myint
.
printf("%d", newpointer);
However, printing to screen *newpointer
will give the contents of the memory location that newpointer
contains, i.e. 5
in this example.
printf("%d", *newpointer);
Why are pointers useful?
Pointers are used to pass values into functions in C.
What does this code snippet do?
#include <stdio.h>
void add(int x, int y){
x = x + 1;
y = y + 1;
}
int main()
{
int x = 2;
int y = 3;
add(x,y);
printf("X is %d, Y is %d",x,y);
}
add
, above, gets its own copy of x
and y
.
main
are unchanged.
x
and y
, we must call by reference.
x
and y
as parameters, if we take their address in memory, i.e. using pointers.
The code is now:
#include <stdio.h>
void add(int *x, int *y){
*x = *x+1;
*y = *y+1;
}
int main()
{
int x = 2;
int y = 3;
add(&x,&y);
printf("X is %d, Y is %d",x,y);
}
In this case, the function call was:
add(&x,&y);
The function is defined as taking pointers to integers, not integers:
void add(int *x, int *y)
The C POSIX Library is the specification of a standard library for POSIX systems; these include Unix-like systems such as Linux. The unistd.h header file provides access to the POSIX OS API, including access to system calls such as fork
. The stdio.h header file declares functions that deal with standard input and output. It includes one function, fdopen(), that is supported only by a POSIX program. The sys/types.h
header file includes definitions for custom datatypes, e.g. structs.
exit()
system call stops the program.
libc
, handles this call.
getpid()
is used to return the current process id of a program.
pid.c
which prints out its process id.
getpid
returns a type pid_t
; it is called like this:
pid_t proc_id; //declare variable called proc_id of type pid_t
proc_id = getpid();
printf("my process id is: %d\n",<variable>)
.
Make sure you can implement this:
#include <stdio.h>
#include <unistd.h>
int main(void){
pid_t proc_id;
proc_id = getpid();
printf("The process ID is: %d\n", pid_t);
return 0;
}
fork
system call will create a new process; it does this by creating a copy of the process that calls it.
fork
is called it will return two integers of type pid_t
, one to the parent process and one to the child process.
fork()
is 0, then the child process is running.
if(return_from_fork == 0){
// code to execute in child
}
else{
// code to execute in parent
}
fork.c
which will create a child process and print out the process IDs of the parent and child.
In the following code, a process is forked.
Each of the two processes then continues to run, each printing out its process ID and a number from 1 to 100.
By running this program, you will see how the processes are interleaved.
The order of printouts will change each time the program is run - the order depends on when the CPU's context switched between the two processes; this is hard to predict.
#include <stdio.h>
#include <unistd.h>
#define MAXIMUM_COUNT 100
void main(void){
pid_t pid;
int i;
fork();
pid = getpid();
for (i = 1; i <= MAXIMUM_COUNT; i++) {
printf("This line is from pid %d, value = %d\n", pid, i);
}
}
Try this yourself.
In the following code, the returned integer from the fork()
function is used to determine if the process is the parent or child.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#define MAXIMUM_COUNT 100
int ChildCode(void); /* Declare a function that will be called if the process is the child process. */
int ParentCode(void); /* Declare a function that will be called if the process is the parent process. */
//Main function with no return, i.e. void.
void main(void)
{
//Declare variable pid of type pid_t.
pid_t pid;
/Call fork() and assign the value to pid.
//Remember that 0 means the child process. The parent will have a different number.
pid = fork();
//If this process is the child process:
if (pid == 0)
ChildCode();
//Else, if it's the parent process:
else
ParentCode();
}
//This is the code for the child process after forking.
int ChildCode(void)
{
int i;
for (i = 1; i <= MAXIMUM_COUNT; i++)
printf("Child process. Count number: %d\n", i);
printf("\n End child process. \n");
return 0; //Success.
}
//This is the code for the parent process after forking.
int ParentCode(void)
{
int i;
for (i = 1; i <= MAXIMUM_COUNT; i++)
printf("Parent process. Count number: %d\n", i);
printf("\n End parent process. \n");
return 0; // Success.
}
Try this yourself.
Once a child process has been created, the programmer can load in a new process image, i.e. a new program, for the child process to execute. This is the main reason that processes are forked. The exec
family of system calls is used to do this. These are:
int execl ( const char *path, const char *arg, ... ); |
int execlp( const char *file, const char *arg, ... ); |
int execle( const char *path, const char *arg, ..., char *const envp[] ); |
int execv ( const char *path, char *const argv[] ); |
int execvp( const char *file, char *const argv[] ); |
int execve( const char *file, char *const argv[], char *const envp[] ); |
The first three system calls are execl
calls, i.e. exec
followed by l
.
execl
accepts a variable number of arguments as a list terminated by a NULL
entry. The execv
calls use an array (or vector) in place of a list of variables.
The variants of execl
and execv
that end in p
will search for the new program files using the PATH
environment variable; those that end in e
accept an array of strings that indicate the environment variables. This array of strings must end with a NULL
, i.e. a Null pointer, as the last entry.
Here, we'll use execl
to load in a new program into the child process. execl
loads in a program using the following syntax:
execl("/hello_world","Hello World",NULL);
Assuming that the "Hello World" program is saved as ./hello_world
in the same directory. The second argument is the program name, not needed mostly. The argument list is terminated by NULL
.
exec
system replaces the current process with one submitted by exec
; in this exercise, use execl
.
test.c
.
Hello!
.
exec.c
that calls execl
to substitute the current process for the Hello! program.
execl
is: execl(<path to program>,<name of program>, NULL);
fcntl.h
; this contains the flags we need.
open("path to file", <flag_1>|<flag_2>
.
open
returns an integer, a file descriptor ID.
O_CREAT
will create the file if it doesn't exist.
O_RDONLY
will open the file for reading.
open.c
to create a file called test.txt
.
close
system call: close(<file descriptor id>);
writer.c
.
O_WRONLY
.
write(<file descriptor>, <string to write>, <size of the
string>
.
For example, write(<file descriptor>, "Hello\n", strlen("Hello\n"));
Don't forget to close the file.
reader.c
char
array in RAM.
char *buffer = (char *) calloc(100, sizeof(char));
O_RDONLY
.
read
is: int bytes = read(<file descriptor id>, <buffer>,
<number of bytes to read>
.
read
returns an integer, i.e. the number of bytes read.
buffer[bytes]='\0' // this terminates the string
.
printf("Read:\n%s/n from file\n",buffer);
.
© Copyright 2022. Please contact Niall McMahon for more.