Guide · Operating Systems
Compiling and running C on the course server
Logging in with ssh and copying with scp, gcc with -o, -Wall, -Wextra and -Werror, reading a compiler message, choosing between man 2 and man 3, and cleaning up the stray processes and IPC objects a broken run leaves behind.
Every program of this course is C on Linux, and it talks to the kernel directly: it forks, sends signals, creates pipes and shared memory. You can write the code anywhere, but compile and test it on Linux – the course server, or a Linux system of your own.
This guide is the routine around the code: logging in, compiling with the right switches, reading what the compiler says, finding the right man page, and cleaning up the processes and IPC objects a broken run leaves behind.
Logging in to the course server
The course server is opsys.inf.elte.hu. The user name is your Neptun code in lowercase, the password is your faculty password. ssh opens a shell on the server: what you type goes there, the output comes back to your terminal (Fig. 1).
abc123 stands for your Neptun code
ssh abc123@opsys.inf.elte.hu # your Neptun code, lowercase
# first connection only: confirm the host key with "yes"
# then type your faculty password – nothing is shown while you type
exit # log out (or Ctrl+D)You can edit on the server with a terminal editor, or edit on your own computer and copy the file over. scp works like cp, with user@host: in front of the remote path; the colon alone means your home directory there.
# from your own computer, not from the server:
scp prog.c abc123@opsys.inf.elte.hu: # into your home directory
scp -r assignment/ abc123@opsys.inf.elte.hu: # a whole directory
scp abc123@opsys.inf.elte.hu:prog.c . # and backCompiling with gcc
gcc prog.c compiles and links in one step. Without other switches the result is called a.out; -o gives it a name. You run it with a path, ./a.out, because the shell looks for commands only in the directories of PATH, and the current directory is not one of them.
gcc prog.c # → a.out
./a.out # run it from the current directory
gcc prog.c -o prog # → prog instead of a.out
./prog 6 # with a command-line argument: argv[1] is "6"Every new gcc run overwrites the previous a.out. Once you have more than one program in a directory, name them with -o – otherwise you test yesterday's binary without noticing.
Warnings: -Wall, -Wextra, -Werror
An error means gcc cannot translate the code: there is no executable. A warning means the code compiles but looks wrong – and the executable is built anyway (Fig. 2). In system programming most real bugs show up as warnings first: a missing #include, a wrong printf format, a variable used before it is set.
| Switch | What it does |
|---|---|
-o name | names the output file instead of a.out |
-Wall | turns on a large set of common warnings that are off by default – despite the name, not all of them |
-Wextra | adds more, e.g. unused parameters and signed/unsigned comparisons |
-Werror | turns every warning into an error: with any warning, no executable is produced. It only affects warnings that are enabled, so combine it with -Wall |
the command to use for every program of the course
gcc -Wall -Wextra -o prog prog.c-Wextra also warns about a function parameter you never use. When that is deliberate – a child function that receives both ends of a pipe but only reads – say so in the code with a cast to void. When it isn't deliberate, the warning has found a bug: a pipe end you meant to use.
pid_t ursula(int pipe_id_rec, int pipe_id_send)
{
(void)pipe_id_send; // deliberately unused – silences -Wextra's warning
...Treat every warning as a bug until you have understood it. A program that compiles with -Wall -Wextra and no warnings has removed a whole class of mistakes before it ever runs.Reading compiler messages
Every message has the same shape: file, line, column, the kind (error, warning, or a note that explains the previous message), the text, and for warnings the switch that enabled it (Fig. 3). Below it gcc prints the source line with a marker under the column.
$ gcc -Wall -Wextra -o kozos kozos_szamlalas.c
kozos_szamlalas.c: In function 'main':
kozos_szamlalas.c:67:5: warning: implicit declaration of function 'wait' [-Wimplicit-function-declaration]
67 | wait(NULL);
| ^~~~This particular warning is one to recognise: implicit declaration of function means the function was called without its header. C then guesses its signature, which can go wrong silently; newer gcc releases reject it as an error. The fix is the #include from the function's man page.
- Fix the first error first. Later errors are often consequences of it – a missing semicolon or brace can produce a screenful.
- Recompile after each fix; the list changes.
- The line number is where gcc noticed the problem. If that line looks fine, look at the line above it.
Man pages: section 2 or 3?
The manual is divided into numbered sections, and the same name can exist in several. For this course four matter:
| Section | Contents | Examples |
|---|---|---|
| 1 | commands | ls, ps, ipcs, the shell's printf |
| 2 | system calls – requests to the kernel | fork, wait, pipe, read, kill, shmget, semop |
| 3 | C library functions | printf, fopen, strcpy, rand, ftok, sigemptyset |
| 7 | overviews | signal, pipe, sysvipc |
man 2 fork # system call
man 3 printf # C library function
man printf # careful: the shell command printf(1)
man 7 signal # overview: every signal and its default action
man -k semaphore # search the one-line descriptionsA page for a function has a fixed layout. SYNOPSIS lists the #include lines and the prototype – the answer to "implicit declaration". RETURN VALUE says how the call reports failure (often -1 and errno, but shmat returns (void *) -1). ERRORS lists the errno values, which is what perror prints in words.
Running – and stopping – your program
A program's exit status is the value it passes to exit or returns from main; the shell shows the last one in $?. That is how you check that the error paths really return EXIT_FAILURE.
./prog
echo $? # exit status of the last command: 0 = successA program that forks can leave processes behind. Ctrl+C sends SIGINT to the whole foreground job, so the children usually end too. But if you kill only the parent, or a child is still blocked on a pipe, a semaphore or a message queue when the parent dies, the child keeps waiting – with a new parent (Fig. 4).
ps -u $USER -o pid,ppid,stat,cmd # your processes with their parents
kill 4101 # ask it to end (SIGTERM)
kill -9 4101 # force it – only if SIGTERM didn't work
pkill -u $USER prog # every process of yours named progCleaning up IPC objects
Message queues, shared memory segments and semaphore sets belong to the kernel, not to your process. A program that ends before its cleanup – a crash, Ctrl+C, an early exit – leaves them behind (Fig. 5).
ipcs # all three kinds: queues, segments, semaphores
ipcs -m # only shared memory (-q: queues, -s: semaphores)
ipcrm -m 32769 # remove a segment by its id
ipcrm -s 3 # a semaphore set
ipcrm -q 5 # a message queueLeftovers are not only clutter. The next run of the same program computes the same key with ftok, and IPC_CREAT opens the old object instead of creating a new one: a message queue still holds the messages of the crashed run, and a segment created with a smaller size makes shmget fail. When the second run behaves differently from the first, run ipcs.
Before you submit
four commands, every time
gcc -Wall -Wextra -o prog prog.c # no warnings left
./prog … # the expected output, also on bad input
ps -u $USER # no prog processes left behind
ipcs # no queues, segments or semaphores left behind- Compile with
-Wall -Wextra; zero warnings, or a(void)cast where a warning is deliberate. - Run it with the expected input, and once with a missing or wrong argument.
- Afterwards,
psshows none of its processes andipcsnone of its objects. - Look up every system call you use in
man 2: the header, the return value on failure, theerrnovalues.