﻿# 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.

Source: https://laszlonemes.com/university/operating-systems/compiling-and-running · Author: László Nemes

Guide · Operating Systems

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.

9 min read `ssh and scp` `gcc -Wall -Wextra` `Errors vs warnings` `man sections` `ps, kill, ipcs, ipcrm`

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.

After this session you can

- log in to the course server with `ssh` and copy files with `scp`
- compile with `gcc -Wall -Wextra -o prog prog.c` and say what each switch does
- read a compiler message: where it is, how serious it is, which switch reported it
- open the right man page: `man 2` for system calls, `man 3` for library functions
- find and remove leftover processes (`ps`, `kill`) and IPC objects (`ipcs`, `ipcrm`)

## 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).

> **Fig. 1** · The terminal is local; the shell, gcc and your files are on the server.
>
> *Diagram:* You work on the course server through ssh: the terminal on your computer shows a shell that runs on the server, where gcc and your files are. scp copies files between your computer and the server.

abc123 stands for your Neptun code

```shell
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.

```shell
# 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 back
```

Good to know

**Windows and macOS**

Current Windows, macOS and Linux all ship an OpenSSH client: `ssh` and `scp` work the same in PowerShell, Terminal or any Linux shell.

## Compiling 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.

```shell
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.

> **Fig. 2** · Errors stop the build. Warnings don't – unless you ask for it.
>
> *Diagram:* An error stops gcc: there is no executable. A warning is printed, but the executable is still built – unless -Werror turns every warning into an error.

| 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

```shell
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.

```c
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.

```text
$ 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);
      |     ^~~~
```

> **Fig. 3** · Where, how serious, what – and which switch reported it.
>
> *Diagram:* A gcc message has a fixed shape: file, line and column, the kind (error, warning or note), the text, and in brackets the switch that enabled the warning. The line and column take you straight to the place in the source.

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` |

```shell
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 descriptions
```

A 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.

Common mistake

**man printf is not the C printf**

Without a section number, `man` shows the first match – for `printf` that is the shell command from section 1. When a page describes options instead of a C prototype, you are in the wrong section: add the `2` or `3`.

## 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`.

```shell
./prog
echo $?           # exit status of the last command: 0 = success
```

A 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).

> **Fig. 4** · Children outlive a killed parent. ps finds them, kill ends them.
>
> *Diagram:* Killing the parent does not kill its children. A child blocked on a pipe, a semaphore or a message queue keeps waiting, is adopted by another process and stays until you find it with ps and end it with kill.

```shell
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 prog
```

Good to know

**Zombies**

A child that has exited but whose parent never called `wait` shows up with state `Z`. It uses no CPU and can't be killed – it is already dead. It disappears when the parent waits for it or exits. The fix is in the code: every child is waited for.

## Cleaning 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).

> **Fig. 5** · IPC objects outlive the program that created them.
>
> *Diagram:* System V IPC objects belong to the kernel, not to the process. If the program ends before its cleanup – a crash or Ctrl+C – the segment, semaphore set and message queue stay. ipcs lists them and ipcrm removes them.

```shell
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 queue
```

Leftovers 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`.

In your assignment

**Clean up in the code, check with ipcs**

The program itself must remove every queue, segment and semaphore set it creates – after waiting for the children, on the normal path and on the error paths after creation. Run your assignment, then `ipcs`: the output should be empty of your objects.

## Before you submit

four commands, every time

```shell
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, `ps` shows none of its processes and `ipcs` none of its objects.
- Look up every system call you use in `man 2`: the header, the return value on failure, the `errno` values.
