Skip to content

C

Linux

Compiling

pkg-config

Compiling a GTK program written in C

gcc -Wall -g helloworld.c -o helloworld \
    $(pkg-config --cflags gtk+-3.0)     \ // (1)
    $(pkg-config --libs gtk+-3.0)         // (2)
  1. pkg-config returns directory names, which gcc will use tto ensure all header files are available.
  2. Here, pkg-config appends options to eh command-line used by the linker including library directory path extensions and a list of libraries needed for linking to the executable.

Syscalls

The open(), close(), read(), and write() syscalls form the heart of low-level file I/O in Linux.

Some system calls accept flag arguments, specified using symbolic constants. Some of these are single-bit values which are combined into a single value using the bitwise OR operator |.

close

close(fd);
#include <fcntl.h>
#include <stdlib.h>
#define BSIZE 16384

void main()
{
    int fin, fout;
    char buf[BSIZE];
    int count;

    if ((fin = open("foo", O_RDONLY)) < 0) {
        perror("foo");
        exit(1);
    }
    if ((fout = open("bar", O_WRONLY | O_CREAT, 0644)) < 0) {
        perror("bar");
        exit(2);
    }
    while ((count = read(fin, buf, BSIZE)) > 0)
        write(fout, buf, count);

    close(fin);
    close(fout);
}

exec

There are seven variations of exec(), all of which are used to replace the current process with the contents of another thread.

These variations are distinguished by how they pass arguments (list or vector), whether or not they create a new environment (e), and whether they require a full pathname or must search on the path environment variable (p). For example, execvpe specifies an executable on the path, creates a new environment, and passes arguments in a vector.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

main()
{
    char line[100];

    while (printf("> "), gets(line) != NULL) {
        if (fork() == 0) {
            execlp(line, line, (char *)0);

            printf("%s: not found\n", line);
            exit(1);
        } else wait(0);
    }
}

exit

exit() ends the program, returning the integer provided in parentheses as the exit status of the process.

fork

fork() is used to create a new process and is typically associated with exec() and wait().

This simple example shows how the value returned by the fork() call differs between the parent and child processes.

#include <stdio.h>

void main() {
    if (fork()) // i.e. anything but 0
        printf("I am the parent\n");
    else 
        printf("I am the child\n");
}

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>

int main() {
  pid_t pid;

  pid = fork();

  if (pid < 0) {
    fprintf(stderr, "Fork Failed");
    return 1;
  } else if (pid == 0) {
    execlp("/bin/ls","ls",NULL);
  } else {
    wait(NULL);
    printf("Child complete");
  }

  return 0;
}

ftruncate

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>

int main() 
{
const int SIZE = 4096;              // size of shared memory object (bytes)
const char *name = "OS";            // name of shared memory object
const char *message_0 = "Hello";
const char *message_1 = "World!";

int fd;     // shared memory file descriptor
char *ptr;  // pointer to shared memory object

    // create the shared memory object
    fd = shm_open(name, O_CREAT | O_RDWR, 0666); 

    // configure size of the shared memory object
    ftruncate(fd, SIZE);    

    // memory map the shared memory object
    ptr = (char *) mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    // write to shared memory object
    sprintf(ptr, "%s", message_0);
    ptr += strlen(message_0);
    sprintf(ptr, "%s", message_1);
    ptr += strlen(message_1);

    return 0;
}

getpid

#include <unistd.h>

getpid();
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int status;
    if (fork()) {
        printf("parent waiting for child ... \n");
        wait(&status);
        if (WIFEXITED(status))
            printf("child ended normally, exit status = %d\n", WEXITSTATUS(status));
        if (WIFSIGNALED(status))
            printf("child terminated by signal %d\n", WTERMSIG(status));
    } else {
        printf("child running -- PID is %d\n", getpid());
        sleep(50);
        exit(3);
    }
}

getrandom

Introduced in Linux 3.17 to allow userspace applications to request random numbers in the case of no access to random devices (i.e. containers). By default it will use the /dev/urandom pool, which normally does not block. A flag can be provided to use the /dev/random pool instead.

lseek

Repositions the file read/write offset, allowing random access to an open file descriptor.

#include <unistd.h>

lseek(
    fd, 
    offset, // (1)
    whence  // (2)
);

  1. Byte offset, positive or negative (usually negative when with respect to end-of file using SEEK_END flag).
  2. Accepts one of several flags that determine where the offset is relative to: SEEK_SET (beginning of file), SEEK_CUR (current position), or SEEK_END (end of file).
--8<-- 

malloc

memcpy

Used for copying stack-allocated data.

memcpy(
    dst,  //
    size, //
    flags //
);

mmap

Maps a file into memory, allowing it to be accessed as if were an array.

mmap(
    addr,   // (1)
    length, // (2)
    prot,   // (3)
    flags,  // (4)
    fd,     // (5)
    offset  // (6)
);  // (7)

  1. Commonly set to NULL, allowing the kernel to choose the address.
  2. Length of the mapping
  3. Typically a combination of PROT_READ and/or PROT_WRITE
  4. Determines whether the mapped region is shared with other processes: MAP_SHARED or MAP_PRIVATE
  5. File descriptor from open()
  6. Multiple of page size, and often set to 0 to map the entire file
  7. Return value is the address to which the file has been mapped (similar to malloc())
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>

int main() 
{
const int SIZE = 4096;              // size of shared memory object (bytes)
const char *name = "OS";            // name of shared memory object
const char *message_0 = "Hello";
const char *message_1 = "World!";

int fd;     // shared memory file descriptor
char *ptr;  // pointer to shared memory object

    // create the shared memory object
    fd = shm_open(name, O_CREAT | O_RDWR, 0666); 

    // configure size of the shared memory object
    ftruncate(fd, SIZE);    

    // memory map the shared memory object
    ptr = (char *) mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    // write to shared memory object
    sprintf(ptr, "%s", message_0);
    ptr += strlen(message_0);
    sprintf(ptr, "%s", message_1);
    ptr += strlen(message_1);

    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main() 
{
const int SIZE = 4096;
const char *name = "OS";
int fd;
char *ptr;

    fd = shm_open(name, O_RDONLY, 0666);
    ptr = (char *) mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    printf("%s", (char *)ptr);

    shm_unlink(name);

    return 0;
}

msync

open

A call to open() creates a new open file descriptor

fd = open("foo", O_RDWR   | // (1) 
                O_TRUNC  | // (2) 
                O_APPEND   // (3)
);
  1. Access mode flag specifying reading and writing: O_RDWR, O_RDONLY, or O_WRONLY.
  2. Open-time flag required for writing. However, calling ftruncate() is recommended over use of this flag in open(), which is retained for backwards compatibility.
  3. Operating mode flag that makes all write operations write data at the end of the file, extending it, regardless of the current file position.
#include <fcntl.h>
#include <stdlib.h>
#define BSIZE 16384

void main()
{
    int fin, fout;
    char buf[BSIZE];
    int count;

    if ((fin = open("foo", O_RDONLY)) < 0) {
        perror("foo");
        exit(1);
    }
    if ((fout = open("bar", O_WRONLY | O_CREAT, 0644)) < 0) {
        perror("bar");
        exit(2);
    }
    while ((count = read(fin, buf, BSIZE)) > 0)
        write(fout, buf, count);

    close(fin);
    close(fout);
}

read

Like write(), calls to read() require require a pointer to the buffer as well as the count of bytes which must not exceed the buffer's actual size. read() will return the number of bytes actually read and 0 on end-of-file.

read(fd, buffer, count);

#include <fcntl.h>
#include <stdlib.h>
#define BSIZE 16384

void main()
{
    int fin, fout;
    char buf[BSIZE];
    int count;

    if ((fin = open("foo", O_RDONLY)) < 0) {
        perror("foo");
        exit(1);
    }
    if ((fout = open("bar", O_WRONLY | O_CREAT, 0644)) < 0) {
        perror("bar");
        exit(2);
    }
    while ((count = read(fin, buf, BSIZE)) > 0)
        write(fout, buf, count);

    close(fin);
    close(fout);
}

shm_open

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>

int main() 
{
const int SIZE = 4096;              // size of shared memory object (bytes)
const char *name = "OS";            // name of shared memory object
const char *message_0 = "Hello";
const char *message_1 = "World!";

int fd;     // shared memory file descriptor
char *ptr;  // pointer to shared memory object

    // create the shared memory object
    fd = shm_open(name, O_CREAT | O_RDWR, 0666); 

    // configure size of the shared memory object
    ftruncate(fd, SIZE);    

    // memory map the shared memory object
    ptr = (char *) mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    // write to shared memory object
    sprintf(ptr, "%s", message_0);
    ptr += strlen(message_0);
    sprintf(ptr, "%s", message_1);
    ptr += strlen(message_1);

    return 0;
}
Removes the shared-memory segment after the consumer has accessed it.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main() 
{
const int SIZE = 4096;
const char *name = "OS";
int fd;
char *ptr;

    fd = shm_open(name, O_RDONLY, 0666);
    ptr = (char *) mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    printf("%s", (char *)ptr);

    shm_unlink(name);

    return 0;
}

wait

wait() blocks until one of the process's children terminates and returns an integer.

int status;
wait(&status); // (1)
  1. Passing 0 or NULL will discard the child's exit status.

The exit status is separted into upper and lower bytes. If the process was killed by a signal the top bit of the lower byte is called the "Core Dumped" flag. There are several macros used to analyze the exit status.

  • WIFEXITED true if child exited normally
  • WEXITSTATUS
  • WIFSIGNALED true if child terminated by signal
  • WTERMSIG signal number
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int status;
    if (fork()) {
        printf("parent waiting for child ... \n");
        wait(&status);
        if (WIFEXITED(status))
            printf("child ended normally, exit status = %d\n", WEXITSTATUS(status));
        if (WIFSIGNALED(status))
            printf("child terminated by signal %d\n", WTERMSIG(status));
    } else {
        printf("child running -- PID is %d\n", getpid());
        sleep(50);
        exit(3);
    }
}
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>

int main() {
  pid_t pid;

  pid = fork();

  if (pid < 0) {
    fprintf(stderr, "Fork Failed");
    return 1;
  } else if (pid == 0) {
    execlp("/bin/ls","ls",NULL);
  } else {
    wait(NULL);
    printf("Child complete");
  }

  return 0;
}

write

Like read(), write() takes an integer file descriptor, a pointer to the buffer, as well as a count of bytes which must not exceed the buffer's size.

write(fd, buffer, count);
#include <fcntl.h>
#include <stdlib.h>
#define BSIZE 16384

void main()
{
    int fin, fout;
    char buf[BSIZE];
    int count;

    if ((fin = open("foo", O_RDONLY)) < 0) {
        perror("foo");
        exit(1);
    }
    if ((fout = open("bar", O_WRONLY | O_CREAT, 0644)) < 0) {
        perror("bar");
        exit(2);
    }
    while ((count = read(fin, buf, BSIZE)) > 0)
        write(fout, buf, count);

    close(fin);
    close(fout);
}

Pthreads

Pthreads provides an API for multithreaded programming in C. In Pthreads, new threads are spawned explicitly using pthread_create passing the name of a function which the thread will run.

Notably, this function must have precisely the following signature:

void *func(void *arg)

Also, when compiling a program using Pthreads with gcc the -lpthread option must be set.

pthread_attr_init

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

int sum;
void *runner(void *param);

int main(int argc, char *argv[])
{
  pthread_t tid;
  pthread_attr_t attr;

  pthread_attr_init(&attr);
  pthread_create(&tid, &attr, runner, argv[1]);
  pthread_join(tid, NULL);

  printf("sum = %d\n", sum);
}

void *runner(void *param)
{
  int i, upper = atoi(param);
  sum = 0;

  for (i = 1; i <= upper; i++)
    sum += i;

  pthread_exit(0);
}

pthread_create

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

int sum;
void *runner(void *param);

int main(int argc, char *argv[])
{
  pthread_t tid;
  pthread_attr_t attr;

  pthread_attr_init(&attr);
  pthread_create(&tid, &attr, runner, argv[1]);
  pthread_join(tid, NULL);

  printf("sum = %d\n", sum);
}

void *runner(void *param)
{
  int i, upper = atoi(param);
  sum = 0;

  for (i = 1; i <= upper; i++)
    sum += i;

  pthread_exit(0);
}

pthread_exit

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

int sum;
void *runner(void *param);

int main(int argc, char *argv[])
{
  pthread_t tid;
  pthread_attr_t attr;

  pthread_attr_init(&attr);
  pthread_create(&tid, &attr, runner, argv[1]);
  pthread_join(tid, NULL);

  printf("sum = %d\n", sum);
}

void *runner(void *param)
{
  int i, upper = atoi(param);
  sum = 0;

  for (i = 1; i <= upper; i++)
    sum += i;

  pthread_exit(0);
}

pthread_join

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

int sum;
void *runner(void *param);

int main(int argc, char *argv[])
{
  pthread_t tid;
  pthread_attr_t attr;

  pthread_attr_init(&attr);
  pthread_create(&tid, &attr, runner, argv[1]);
  pthread_join(tid, NULL);

  printf("sum = %d\n", sum);
}

void *runner(void *param)
{
  int i, upper = atoi(param);
  sum = 0;

  for (i = 1; i <= upper; i++)
    sum += i;

  pthread_exit(0);
}

Structs

task_struct

Represents the process control block

Tasks

GTK

Hello, World!

#include  <gtk/gtk.h>

int main (int argc, char *argv[])
{
    GtkWidget *window;

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Hello, World!");
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_widget_set_size_request (window, 300, 300);

    g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); // (1)

    GtkWidget *label = gtk_label_new ("Hello, World!");

    gtk_container_add (GTK_CONTAINER (window), label);
    gtk_widget_show_all (window);

    gtk_main ();
    return 0;
}
  1. Without connecting the signals, the process will not terminate after clicking the close button, although the window will close.