Operations 44 min read

Understanding Linux Processes: Concepts, Monitoring, Management, and Inter‑Process Communication

This article explains Linux process fundamentals, including their definition, attributes, states, priority handling, common monitoring commands, control techniques, and various inter‑process communication mechanisms with practical code examples.

Deepin Linux
Deepin Linux
Deepin Linux
Understanding Linux Processes: Concepts, Monitoring, Management, and Inter‑Process Communication

In the Linux world, a process is an active entity that executes a program instance; the system creates a process for each program run, turning static code and data into a dynamic execution context with identifiable attributes such as PID, state, and priority.

Each process has a unique Process ID (PID) similar to an ID card, and can be in states like running (R), sleeping (S), stopped (T), or zombie (Z). The priority, adjustable via the nice value (-20 to 19), determines CPU scheduling order.

1. Linux Process Concepts

From a theoretical perspective, a process abstracts a running program. From an implementation perspective, it is a data structure that models the dynamic behavior of a program in memory.

A process consists of three parts: the program code, its data, and a Process Control Block (PCB) that uniquely identifies the process to the kernel.

1.1 What Is a Process?

Narrow definition: a process is the execution of a program.

Broad definition: a process is a program with an independent function that runs on a specific data set; it is the basic allocation and execution unit of an operating system.

Process characteristics:

Dynamic: created and destroyed during execution.

Concurrent: can run alongside other processes.

Independent: a separate unit for resource allocation and scheduling.

Asynchronous: execution may be interrupted by external events.

Structural: composed of program, data, and PCB.

Multiple processes can share the same program binary but operate on different data.

1.2 Linux Process Structure

A Linux process consists of three segments: code, data, and stack, together with a PCB (Process Control Block). The PCB is represented by the task_struct structure defined in include/linux/sched.h , and each new process allocates a fresh task_struct stored in the task[] array.

1.3 Process vs. Program

A program is a static collection of instructions and data; a process is the dynamic execution of that program.

Programs persist on disk; processes have a limited lifetime.

A process contains a PCB, code segment, and data segment; a program does not.

Processes can create other processes; programs cannot.

The same program can spawn multiple processes, each with its own PID.

In traditional OSes, only processes can be scheduled and allocated resources.

2. Process Viewing and Monitoring

Understanding process states is essential for system administration and debugging. Various commands provide insight into running processes.

2.1 ps Command

The ps command lists detailed information about active processes. For example, ps -aux shows all users' processes, including PID, CPU usage, memory usage, and command.

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  16120  3340 ?        Ss   08:21   0:01 /sbin/init splash
root         2  0.0  0.0      0     0 ?        S    08:21   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        S    08:21   0:00 [ksoftirqd/0]

Key columns: USER (owner), PID (process ID), %CPU, %MEM, VSZ (virtual memory), RSS (resident set), TTY, STAT (state), START (start time), TIME (CPU time), COMMAND.

The ps -ef command also displays all processes in a slightly different format.

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 08:21 ?        00:00:01 /sbin/init splash
root         2     0  0 08:21 ?        00:00:00 [kthreadd]
root         3     2  0 08:21 ?        00:00:00 [ksoftirqd/0]

2.2 top Command

top provides a real‑time, continuously refreshed view of system processes, showing CPU load, memory usage, and per‑process resource consumption.

Interactive keys allow sorting by CPU (P), memory (M), PID (N), filtering by user (u), and terminating a process (k). Press q to quit.

2.3 pstree Command

pstree displays the parent‑child relationship of processes as a tree.

systemd─┬─ModemManager───2*[{ModemManager}]
        ├─NetworkManager─┬─dhclient
        │                ├─dnsmasq───2*[{dnsmasq}]
        │                └─2*[{NetworkManager}]
        ├─agetty
        ├─atd
        ├─auditd───{auditd}
        ├─chronyd

Use pstree -p PID to show PIDs, or pstree -u username to view a specific user's process tree.

3. Process Control and Management

3.1 Process Start and Termination

Starting a process is typically done by typing a command at the shell prompt; the command runs in the foreground until it finishes.

Events that create processes:

System initialization.

Execution of a program that spawns a new process.

User request to start a new process.

Events that end processes:

Normal completion.

Self‑termination due to an error.

Termination by another process.

Forced termination (e.g., SIGKILL).

Appending & runs a command in the background, freeing the terminal for other commands. The background job remains a child of the shell; if the shell exits, it receives a SIGHUP.

To move a foreground job to the background, press Ctrl+Z to suspend, then bg to resume in the background. Use fg %jobid to bring it back to the foreground.

The kill command terminates a process by PID; kill -9 PID sends SIGKILL for forced termination. killall process_name terminates all processes matching the given name.

3.2 Process Priority Adjustment

Linux priorities range from -20 (highest) to 19 (lowest). The nice value adjusts the priority; a lower nice value means higher priority.

Use ps -l to view PRI (actual priority) and NI (nice value). The kernel calculates PRI = PRI(old) + nice , with a default of 80.

Start a process with a specific priority using nice -n -10 top . Adjust an existing process with renice -n 5 12345 to lower its priority.

3.3 Inter‑Process Communication (IPC)

Processes often need to exchange data. Common IPC mechanisms include pipes, named pipes (FIFOs), shared memory, and message queues.

1) Pipe (unnamed) : Half‑duplex, parent‑child only. Example:

#include
#include
#include
#include
#include
int main(void) {
    char buf[32] = {0};
    pid_t pid;
    int fd[2];

    if (pipe(fd) == -1) {
        perror("pipe");
        return 1;
    }

    pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid > 0) {
        close(fd[0]);
        write(fd[1], "hello", 5);
        close(fd[1]);
        wait(NULL);
    } else {
        close(fd[1]);
        read(fd[0], buf, 32);
        printf("buf is %s\n", buf);
        close(fd[0]);
    }
    return 0;
}

2) Named Pipe (FIFO) : Half‑duplex, can be used between unrelated processes. Example:

// writer.c
#include
#include
#include
#include
#include
#include
int main() {
    int fd;
    char cont_w[] = "hello sundy";
    if (mkfifo("myfifo", 0666) == -1) {
        perror("mkfifo");
        return 1;
    }
    fd = open("myfifo", O_WRONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    write(fd, cont_w, sizeof(cont_w));
    close(fd);
    return 0;
}
// reader.c
/*
#include
#include
#include
#include
#include
#include
int main() {
    int fd;
    char buf[32] = {0};
    fd = open("myfifo", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    read(fd, buf, sizeof(buf));
    printf("buf is %s\n", buf);
    close(fd);
    return 0;
}
*/

3) Shared Memory : Fastest IPC by mapping the same memory region into multiple processes.

#include
#include
#include
#include
#include
#include
#define SHM_SIZE 1024

int main() {
    key_t key;
    int shmid;
    char *shm, *s;
    key = ftok(".", 'a');
    if (key == -1) { perror("ftok"); return 1; }
    shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) { perror("shmget"); return 1; }
    shm = (char *)shmat(shmid, NULL, 0);
    if (shm == (char *)-1) { perror("shmat"); return 1; }
    s = shm;
    for (int i = 0; i < 10; i++) {
        sprintf(s, "This is %d\n", i);
        s += strlen(s);
    }
    while (*shm != '*') { sleep(1); }
    if (shmdt(shm) == -1) { perror("shmdt"); return 1; }
    if (shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl"); return 1; }
    return 0;
}

#include
#include
#include
#include
#include
#include
#define SHM_SIZE 1024

int main() {
    key_t key;
    int shmid;
    char *shm, *s;
    key = ftok(".", 'a');
    if (key == -1) { perror("ftok"); return 1; }
    shmid = shmget(key, SHM_SIZE, 0666);
    if (shmid == -1) { perror("shmget"); return 1; }
    shm = (char *)shmat(shmid, NULL, 0);
    if (shm == (char *)-1) { perror("shmat"); return 1; }
    s = shm;
    while (*s != '\0') { printf("%s", s); s += strlen(s); }
    *shm = '*';
    if (shmdt(shm) == -1) { perror("shmdt"); return 1; }
    return 0;
}

4) Message Queue : Stores messages in the kernel, each with a type identifier.

#include
#include
#include
#include
#include
#include
#define MSG_SIZE 128

typedef struct msgbuf {
    long mtype;
    char mtext[MSG_SIZE];
} msgbuf;

int main() {
    key_t key;
    int msgid;
    msgbuf msg;
    key = ftok(".", 'b');
    if (key == -1) { perror("ftok"); return 1; }
    msgid = msgget(key, IPC_CREAT | 0666);
    if (msgid == -1) { perror("msgget"); return 1; }
    msg.mtype = 1;
    strcpy(msg.mtext, "Hello, message queue!");
    if (msgsnd(msgid, &msg, strlen(msg.mtext) + 1, 0) == -1) { perror("msgsnd"); return 1; }
    printf("Message sent.\n");
    return 0;
}
#include
#include
#include
#include
#include
#include
#define MSG_SIZE 128

typedef struct msgbuf {
    long mtype;
    char mtext[MSG_SIZE];
} msgbuf;

int main() {
    key_t key;
    int msgid;
    msgbuf msg;
    key = ftok(".", 'b');
    if (key == -1) { perror("ftok"); return 1; }
    msgid = msgget(key, 0666);
    if (msgid == -1) { perror("msgget"); return 1; }
    if (msgrcv(msgid, &msg, MSG_SIZE, 1, 0) == -1) { perror("msgrcv"); return 1; }
    printf("Received message: %s\n", msg.mtext);
    return 0;
}

4. Special Process Analysis

4.1 Daemon Processes

Daemons (or services) run in the background without a controlling terminal, starting at boot and continuing until shutdown. Examples include syslogd (system logging), sshd (remote login), httpd (web server), and mysqld (database server).

Management tools such as systemctl (systemd) or nohup can start, stop, enable, or disable daemons.

4.2 Zombie and Orphan Processes

A zombie process occurs when a child exits but the parent has not called wait or waitpid , leaving an entry in the process table. Accumulated zombies can exhaust PID resources.

Example of creating a zombie:

#include
#include
#include
int main() {
    pid_t pid = fork();
    if (pid < 0) { perror("fork"); exit(1); }
    else if (pid == 0) { printf("Child process, PID: %d\n", getpid()); exit(0); }
    else { printf("Parent process, PID: %d\n", getpid()); while (1) { sleep(1); } }
    return 0;
}

Fix by having the parent wait for the child:

#include
#include
#include
#include
int main() {
    pid_t pid = fork();
    if (pid < 0) { perror("fork"); exit(1); }
    else if (pid == 0) { printf("Child process, PID: %d\n", getpid()); exit(0); }
    else { printf("Parent process, PID: %d\n", getpid()); int status; waitpid(pid, &status, 0); printf("Child process has exited\n"); }
    return 0;
}

Alternatively, handle SIGCHLD in the parent:

#include
#include
#include
#include
#include
void sigchld_handler(int signo) {
    pid_t pid; int status;
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        printf("Child process %d has exited\n", pid);
    }
}

int main() {
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction"); exit(1); }
    pid_t pid = fork();
    if (pid < 0) { perror("fork"); exit(1); }
    else if (pid == 0) { printf("Child process, PID: %d\n", getpid()); exit(0); }
    else { printf("Parent process, PID: %d\n", getpid()); while (1) { sleep(1); } }
    return 0;
}

An orphan process occurs when its parent exits before it. The init (or systemd) process adopts the orphan, preventing resource leaks.

#include
#include
#include
int main() {
    pid_t pid = fork();
    if (pid < 0) { perror("fork"); exit(1); }
    else if (pid == 0) { printf("Child process, PID: %d, PPID: %d\n", getpid(), getppid()); sleep(5); printf("Child process, PID: %d, PPID: %d\n", getpid(), getppid()); }
    else { printf("Parent process, PID: %d\n", getpid()); exit(0); }
    return 0;
}

When the parent exits, the child’s PPID becomes 1 (init), indicating it is now an orphan.

5. Linux Process Practical Exercises

5.1 Case 1: Optimize System Performance

When a server becomes sluggish, use top to identify high‑CPU processes. In the example, a Python script big_computation.py consumes 80% CPU.

top - 10:15:23 up 2 days,  1:23,  2 users,  load average: 2.50, 2.00, 1.50
Tasks: 200 total,   2 running, 198 sleeping,   0 stopped,   0 zombie
%Cpu(s): 80.0 us, 10.0 sy,  0.0 ni,  5.0 id,  5.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7914.2 total,    123.4 free,   6543.2 used,   1247.6 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,     0.0 used.   1024.0 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
12345 user1    20   0  512340 204800  12340 R  80.0  2.5   5:10.00 python big_computation.py

If the process is not critical, terminate it:

ps -ef | grep big_computation.py
user1   12345  1  80 10:00 ?   00:05:10 python big_computation.py
kill 12345

Alternatively, lower its priority with renice -n 10 12345 so other processes get more CPU time.

5.2 Case 2: Implement IPC with Pipes

The following C program demonstrates a producer that generates random numbers and a consumer that squares them using an unnamed pipe.

#include
#include
#include
#include
int main() {
    int pipe_fd[2];
    if (pipe(pipe_fd) == -1) { perror("pipe"); return 1; }
    pid_t pid = fork();
    if (pid < 0) { perror("fork"); return 1; }
    else if (pid == 0) {
        close(pipe_fd[1]);
        int num;
        while (read(pipe_fd[0], #, sizeof(int)) > 0) {
            int result = num * num;
            printf("Processed result: %d\n", result);
        }
        close(pipe_fd[0]);
    } else {
        close(pipe_fd[0]);
        srand(time(NULL));
        for (int i = 0; i < 5; i++) {
            int num = rand() % 100;
            write(pipe_fd[1], #, sizeof(int));
            printf("Generated number: %d\n", num);
            sleep(1);
        }
        close(pipe_fd[1]);
        wait(NULL);
    }
    return 0;
}

Compile and run:

gcc -o producer producer.c
./producer

Sample output:

Generated number: 34
Processed result: 1156
Generated number: 78
Processed result: 6084
Generated number: 12
Processed result: 144
Generated number: 91
Processed result: 8281
Generated number: 56
Processed result: 3136

This demonstration shows how a pipe can be used for simple inter‑process data transfer.

Process ManagementLinuxIPCsystem monitoringShell Commands
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.