Everything you have handled so far was static: files, permissions, text. This lesson changes its object and deals with what is alive. A process is a program in execution, with its own memory, its open file descriptors, its identity and its place in a family tree that starts at systemd. Understanding them is what lets you answer the question Marta is going to ask you sooner or later: "why is the server slow?".

In the previous lesson you discovered that 65% of the errors in access.log are concentrated in the 03:00 slot, and that everything points to something competing for the database connections during the night-time window. Here are the tools to find out what that something is, and to act on it without bringing the service down.

Contents

  1. What a process is
  2. The process tree
  3. Life cycle: fork, exec, wait, exit
  4. Zombies and orphans
  5. Process states
  6. ps properly
  7. top and htop: reading the header
  8. pgrep and pkill
  9. Signals
  10. Priorities: nice, renice and ionice
  11. Job control in the interactive session
  12. lsof and fuser
  13. The Tramontana case: the app hanging in the early hours

  1. What a process is

Every process has a record in the kernel containing, among other things:

Attribute What it is
PID unique identifier
PPID the parent process's PID
Effective UID/GID the identity against which permissions are checked
State R, S, D, T or Z (section 5)
Priority (PRI) and nice (NI) how much CPU it gets
VSZ / RSS virtual memory reserved / physical memory actually used
Working directory the cwd it resolves relative paths against
Open descriptors the ones you saw in /proc/<pid>/fd in 03-04

The distinction between VSZ and RSS is the one that causes the most misunderstandings: VSZ includes memory reserved but never touched and shared libraries, so adding up the VSZ of every process gives a number far above the installed RAM without that meaning anything at all. The number that matters is RSS.

  1. The process tree

Every process descends from another. The root is systemd, with PID 1, the first process the kernel starts.

operator@srv-tramontana:~$ pstree -p | head -8
systemd(1)─┬─cron(742)
           ├─dbus-daemon(701)
           ├─executable(1284)─┬─{executable}(1285)
           │                  ├─{executable}(1286)
           │                  └─{executable}(1287)
           ├─sshd(889)───sshd(1502)───sshd(1508)───bash(1509)───pstree(1620)
           └─systemd-journald(412)

There is a lot to read there. executable(1284) is the Tramontana application with three threads (in braces, they are not independent processes). The sshd chain shows your own connection: the main daemon, the connection's process, the one for the already-authenticated session, your bash and the pstree you have just launched as its child. There is the environment inheritance from 03-01, turned into a diagram.

pstree -ps 1284 shows the ancestors of a specific PID and returns systemd(1)───executable(1284): the app hangs directly off systemd, not off a user session. That means it survives you closing the SSH connection, and it is the difference between a service and a program launched by hand.

  1. Life cycle: fork, exec, wait, exit

flowchart LR
    P["Parent (bash)"] -->|"fork()"| C["Child: a copy of the parent<br/>new PID, same code"]
    C -->|"exec()"| N["The child replaces itself<br/>with the new program"]
    N -->|"exit(code)"| Z["Zombie: only the exit<br/>code is left"]
    P -->|"wait()"| Z
    Z --> F["Released from the<br/>process table"]

When you type ls in Bash this is what happens: Bash calls fork(), which creates a child identical to itself; the child calls exec(), which replaces its contents with the ls binary while keeping the PID and the open descriptors — that is where the redirections Bash prepared beforehand fit in; ls finishes by calling exit(0); and Bash, which was in wait(), collects that exit code, which is what you later read in $?.

That separation between fork and exec is what makes redirection possible in the way you studied it: there is a moment, between the two calls, when the child already exists and is not yet the new program, and that is where the shell reconfigures the descriptors.

  1. Zombies and orphans

They are two opposite situations and it is worth not confusing them.

A zombie (state Z) is a process that has already finished but whose parent has not called wait(). It consumes no CPU or memory: it merely occupies an entry in the process table with its exit code, waiting for somebody to collect it.

A zombie cannot be killed. It is already dead; kill -9 on it does nothing, because signals are received by living processes. The only way to get rid of it is for its parent to collect it or for the parent to die. A handful of zombies is irrelevant; thousands indicate a badly written parent, and the correct action is to restart the parent.

To look for them: ps -eo stat,ppid,pid,comm | awk '$1 ~ /^Z/'. On srv-tramontana it returns nothing.

An orphan is the opposite: a living process whose parent has died. It is not a problem. The kernel reassigns it to systemd (the PPID becomes 1), which does call wait() correctly. It is the mechanism nohup and daemons rely on.

  1. Process states

State Name Means
R Running / runnable running or ready to run
S Interruptible sleep waiting for something (I/O, network, a timer); most of them
D Uninterruptible sleep waiting for disk I/O, does not accept signals
T Stopped stopped by Ctrl+Z or SIGSTOP
Z Zombie finished, pending collection

Modifiers you will see attached: s session leader, l multi-threaded, + in the foreground, < high priority, N low priority.

Why a process in D cannot be killed. It is blocked inside the kernel waiting for a disk or network operation to complete, and the kernel does not deliver signals to it until that operation finishes. kill -9 is queued: the signal will be delivered when the process returns to state S or R. If a process has spent minutes in D, the problem is not the process: it is the storage — a failing disk, a dead NFS mount — and that is where you have to look.

  1. ps properly

ps has two historical syntaxes that coexist: BSD (without a hyphen) and UNIX (with one). The two combinations that get used:

operator@srv-tramontana:~$ ps aux | head -3
USER         PID %CPU %MEM    VSZ   RSS TTY   STAT START   TIME COMMAND
root           1  0.0  0.4 168404 12856 ?     Ss   08:31   0:03 /sbin/init
svc-tram+   1284  2.1 12.4 982340 483920 ?    Ssl  08:32   1:47 /opt/tramontana/app/executable
Column Means
USER the effective owner (truncated to 8 characters: svc-tram+)
%CPU percentage of CPU averaged since it started, not instantaneous
%MEM percentage of physical RAM
VSZ / RSS virtual / resident memory, in KiB
TTY the associated terminal; ? means it has none, typical of a service
STAT the state plus modifiers
START / TIME start time / total CPU consumed

Careful with %CPU: it is an average since the process started. A process that devoured the CPU eight hours ago and is now asleep may still show a high percentage. To find out what is consuming CPU right now, use top.

ps -ef gives the UNIX view, with an explicit PPID, which is what you want for following parent-child relationships. And ps -o builds custom output:

operator@srv-tramontana:~$ ps -eo pid,ppid,user,ni,stat,rss,etime,cmd --sort=-rss | head -4
    PID    PPID USER      NI STAT   RSS     ELAPSED CMD
   1284       1 svc-tram   0 Ssl 483920    04:12:33 /opt/tramontana/app/executable
    889       1 root       0 Ss   12404    04:13:05 sshd: /usr/sbin/sshd -D
    412       1 root       0 Ss   11208    04:13:11 systemd-journald

--sort=-rss sorts by resident memory in descending order (the - reverses it). etime gives the time elapsed since it started, far more useful than the absolute time when you are diagnosing.

Filters: -u operator by user, -C executable by command name, -p 1284 by PID, --forest to see the hierarchy in ps's own output.

  1. top and htop: reading the header

top - 12:44:18 up  4:13,  2 users,  load average: 3.42, 1.87, 0.94
Tasks: 132 total,   2 running, 130 sleeping,   0 stopped,   0 zombie
%Cpu(s): 12.3 us,  4.1 sy,  0.0 ni, 18.2 id, 64.8 wa,  0.3 hi,  0.3 si,  0.0 st
MiB Mem :   3844.0 total,    198.4 free,   2914.2 used,    731.4 buff/cache
MiB Swap:   2048.0 total,   1620.0 free,    428.0 used.    612.8 avail Mem

The load average

The three numbers are the average of processes in state R or D over 1, 5 and 15 minutes. Two consequences that almost nobody is clear about:

  1. It is not a percentage. It has to be compared with the number of cores: on the srv-tramontana VM, with 2 vCPUs, a load of 2.00 is full occupancy and 3.42 means there is more work than the machine can handle.
  2. It includes processes in D, that is, those waiting on disk. A load of 3.42 with the CPU almost idle does not indicate a lack of CPU: it indicates that there are processes stuck waiting on I/O.

And comparing the three numbers gives you the trend: 3.42, 1.87, 0.94 is a rising curve. The problem is getting worse right now, not easing off.

The CPU breakdown

Abbreviation Means
us time in user code
sy time in the kernel
ni processes with a modified nice value
id idle
wa waiting for I/O: the CPU is free but cannot make progress
hi / si hardware / software interrupts
st steal: CPU the hypervisor gave to another VM

That 64.8 wa is the figure in the header above. The CPU is not saturated: it is waiting for the disk or for the database. Chasing a process that consumes CPU would be looking in the wrong place. And a high st on a virtual machine means the problem is not in your VM but on the host, something worth knowing before optimising code.

On memory: buff/cache is not lost memory, it is disk cache the kernel releases as soon as anybody needs it. The figure that matters is avail Mem. Seeing little free memory on Linux is normal and desirable.

Useful keys in top: M sorts by memory, P by CPU, 1 breaks it down per core, k sends a signal, u filters by user, c shows the full command line. htop does the same with colours, mouse support and a tree with F5; it is installed separately but it is worth it.

  1. pgrep and pkill

operator@srv-tramontana:~$ pgrep -a executable
1284 /opt/tramontana/app/executable --config /etc/tramontana/app.conf

-a shows the command line, -c counts, -u filters by user, -f matches against the full line and not just the name, -x requires an exact match, -n/-o the newest / the oldest.

Why they are better than ps | grep | awk | kill: that pipeline has two flaws. The first is that grep itself appears in the list and you can end up killing a PID that no longer exists or, worse, a recycled one. The second is that a partial match takes out processes you did not mean to touch. pgrep queries /proc directly and does not include itself.

Before pkill, always run the equivalent pgrep. It is the same convention as ls before rm:

operator@srv-tramontana:~$ pgrep -a -f 'executable --config'
1284 /opt/tramontana/app/executable --config /etc/tramontana/app.conf
operator@srv-tramontana:~$ pkill -f 'executable --config'

  1. Signals

A signal is an asynchronous notification the kernel delivers to a process.

No. Name Default effect Can it be caught?
1 SIGHUP terminate; by convention, reload the configuration yes
2 SIGINT interrupt (Ctrl+C) yes
3 SIGQUIT terminate and dump core (Ctrl+\) yes
9 SIGKILL kill immediately no
15 SIGTERM terminate cleanly (the default) yes
18/19 SIGCONT / SIGSTOP resume / stop CONT yes, STOP no
10/12 SIGUSR1 / SIGUSR2 free, defined by the application yes

kill -l lists the system's 64 signals. kill 1284 sends SIGTERM. For any other: kill -TERM 1284, kill -15 1284 or kill -s SIGTERM 1284, all equivalent. killall name acts by name instead of by PID — with the obvious risk of reaching more than you intended.

The professional rule: SIGTERM, wait, and only then SIGKILL

SIGTERM is a request: the process receives it, runs its shutdown routine and terminates when it has finished. SIGKILL is a summary execution: the kernel destroys the process without telling it. The process never finds out and therefore can do nothing.

What you lose with -9:

  • The data in buffers that had not yet been written to disk.
  • The open database transactions, which are left uncommitted and locking rows until they expire.
  • The HTTP requests in flight, which are cut off dead: the client sees an error.
  • The temporary files and the lock files, which are left orphaned and can prevent the next start-up.
  • The chance for the process to write in the log why it shut down.

The correct procedure:

operator@srv-tramontana:~$ kill -TERM 1284
operator@srv-tramontana:~$ sleep 10; pgrep -c executable
0

Ten seconds is a reasonable margin for a web application; if the process is gone, we have finished cleanly. Only if it is still alive after that period do you resort to kill -9, and then it is documented as an incident, because it means the application's shutdown routine does not work and that is a defect that has to be fixed.

SIGHUP deserves a mention: by convention, many daemons interpret it as "reread your configuration without restarting". When it works, it is the way to apply a change in app.conf without dropping a single request.

  1. Priorities: nice, renice and ionice

The nice value runs from -20 (highest priority) to 19 (lowest). The name comes from "being nice": the higher it is, the more the process gives way to the others. A normal user can only raise it — lower their own priority; reducing it requires root.

operator@srv-tramontana:~$ nice -n 15 tar -czf /srv/tramontana/backups/outgoing/data.tar.gz /home/operator/data
operator@srv-tramontana:~$ sudo renice -n 5 -p 1284
1284 (process ID) old priority 0, new priority 5
operator@srv-tramontana:~$ ionice -c 3 tar -czf /srv/tramontana/backups/outgoing/data.tar.gz /home/operator/data

nice launches a command with a modified priority; renice changes that of a process already running, also by user (-u) or by group. And for I/O, which in our case is the scarce resource, there is ionice. Its class 3 is idle: the backup only reads from the disk when nobody else needs it. With a wa of 64.8%, ionice solves more than nice: lowering the CPU priority of a process that does not use CPU achieves nothing. Diagnosing before acting also means choosing the right lever.

  1. Job control in the interactive session

Action How
Launch in the background command &
List the session's jobs jobs -l
Bring to the foreground fg %1
Continue in the background bg %1
Suspend the current one Ctrl+Z (sends SIGSTOP)
Detach from the session disown -h %1
Immune to the session closing nohup command &
operator@srv-tramontana:~$ tar -czf /tmp/backup.tar.gz /opt/tramontana/releases/3.1.0 &
[1] 2041
operator@srv-tramontana:~$ jobs -l
[1]+  2041 Running     tar -czf /tmp/backup.tar.gz /opt/tramontana/releases/3.1.0 &

When the session closes, the shell sends SIGHUP to its jobs. nohup makes them immune and redirects the output to nohup.out; disown -h achieves the same for an already-launched job.

And now the important warning: none of this is any use for a real service. A nohup ./executable & does not restart if the process dies, does not start when the server boots, has no resource control, does not manage its logs and cannot be stopped cleanly by anybody but you. Job control is for long tasks in your session — a backup, a compilation — not for production. Services are managed with systemd, and that is lesson 05-05.

  1. lsof and fuser

lsof ("list open files") answers the question "who has this open?". Since on Linux almost everything is a file, it also works for sockets and ports.

The "I cannot unmount" case.

operator@srv-tramontana:~$ sudo umount /srv/tramontana/backups
umount: /srv/tramontana/backups: target is busy.
operator@srv-tramontana:~$ sudo lsof +D /srv/tramontana/backups
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
bash     1509 opera  cwd    DIR  8,1      4096 262148 /srv/tramontana/backups/outgoing
tar      2041 opera    3w   REG  8,1  10485760 262203 /srv/tramontana/backups/outgoing/data.tar.gz

Two culprits: a bash whose working directory (cwd) is inside it — a cd out of there is enough — and a tar writing (3w, descriptor 3 in write mode). With that you already know what to expect and who to warn, instead of forcing the unmount.

The "port 8080 is in use" case.

operator@srv-tramontana:~$ sudo lsof -i :8080
COMMAND     PID           USER   FD   TYPE  DEVICE NODE NAME
executable 1284 svc-tramontana   7u  IPv4 1284091  TCP *:http-alt (LISTEN)

PID 1284 has the port listening. It is the direct answer to "I cannot start the new application because the port is in use": the old one is still alive.

fuser is terser and very practical for taking action: fuser -v /path lists the processes, fuser -k /path kills them (carefully) and fuser -k -TERM 8080/tcp sends SIGTERM to whoever is occupying that port.

/proc/<pid>/ is the source of truth that all these tools draw on: cmdline the exact command, environ the environment it started with, cwd and exe as symbolic links, fd/ the descriptors, status a readable summary, limits the resource limits. When a tool gives you a doubtful figure, go to the file.

  1. The Tramontana case: the app hanging in the early hours

03:05. The application is not responding. Marta messages you. Here is the procedure, with the conclusion from 03-05 as the starting hypothesis.

Step 1: is it alive and in what state?

operator@srv-tramontana:~$ ps -o pid,stat,ni,rss,etime,pcpu -p $(pgrep -x executable)
    PID STAT  NI   RSS     ELAPSED %CPU
   1284 Dsl    0 483920    04:12:33  2.1

State D: it is not hung in a loop, it is blocked waiting for I/O. With only 2.1% CPU, it is not a computation problem. A kill -9 here would not even take effect immediately.

Step 2: what does the load say? The load average: 3.42, 1.87, 0.94 with 64.8 wa from the header in section 7 confirms the same thing from another angle: a growing queue and I/O waiting, not a lack of CPU.

Step 3: who is competing?

operator@srv-tramontana:~$ ps -eo pid,user,stat,pcpu,etime,cmd --sort=-pcpu | head -4
    PID USER     STAT %CPU     ELAPSED CMD
   2210 operator D    38.4       05:12 tar -czf /srv/tramontana/backups/outgoing/nightly.tar.gz /opt/tramontana
   1284 svc-tram Dsl   2.1    04:12:33 /opt/tramontana/app/executable

There it is. A nightly backup has spent five minutes reading the whole of /opt/tramontana — including the four releases with their 48 MB executable files — and is saturating the VM's disk. The application, which needs the disk to serve queries, is left waiting.

Step 4: relieve it without killing anything. The backup is legitimate; what is wrong is its priority.

operator@srv-tramontana:~$ sudo ionice -c 3 -p 2210
operator@srv-tramontana:~$ sudo renice -n 19 -p 2210
2210 (process ID) old priority 0, new priority 19

Both changes are applied to the running process, without interrupting it. Two minutes later the wa drops and the application responds again.

Step 5: if restarting the app had been necessary, the procedure would have been kill -TERM $(pgrep -x executable), waiting for the requests in flight to finish, verifying with pgrep that it is gone and only then starting it again. Never kill -9 as the first move: it would cut off any bookings that were being confirmed at that moment, and a half-finished booking is a problem with a customer standing in front of you.

The report for Marta. The outage was not an application failure but competition for the disk: the nightly backup was running at the same priority as the service and was leaving it without access to storage. The measure applied — giving the backup the lowest I/O priority — protects against it blocking the service again, and does not protect against a general increase in load: if the number of bookings grows, more resources will be needed or the database will have to be separated out. In addition, the backup includes all four releases when the active one would be enough, which makes it unnecessarily heavy. Outstanding: adjust the scope of the backup and its schedule, which is exactly what we will look at in the next lesson.

Common Mistakes and Tips

  • Using kill -9 as the first option. It is the last. SIGTERM, wait, verify.
  • Trying to kill a zombie. It is already dead. Act on the parent.
  • Persisting with a process in D. It will not receive the signal until the I/O finishes. Investigate the storage.
  • Reading ps's %CPU as an instantaneous value. It is an average since start-up. Use top.
  • Being alarmed by low free memory. buff/cache is reusable cache. Look at avail Mem.
  • Comparing the load average with 100. Compare it with the number of cores, and remember it includes I/O waiting.
  • ps | grep | kill on a short name. grep itself shows up in the list and a partial match kills too much. Use pgrep -a and then pkill.
  • Leaving a service running with nohup &. It does not survive a reboot and does not recover by itself.
  • Tip: faced with "the server is slow", always look at top's wa first. If it is high, the bottleneck is the disk and half the hypotheses are ruled out at a stroke.
  • Tip: ps -eo accepts any combination of columns; keep an alias with your own in ~/.bashrc, as you learned in 03-01.

Exercises

Exercise 1. Find out with what exact command line, with what working directory and with what environment variables the Tramontana process started, without using ps. Explain what each piece of information tells you.

Exercise 2. Launch a long task in the background, suspend it, resume it in the background, lower its CPU and I/O priority, and detach it from your session so that it survives an SSH disconnection. Show the verification of each step.

Exercise 3. Simulate the diagnosis of "port 8080 will not accept the new version": identify which process is occupying it, who it belongs to, since when, and stop that process cleanly, verifying that it has finished before treating the step as done.

Solutions

Solution 1.

operator@srv-tramontana:~$ PID=$(pgrep -x executable)
operator@srv-tramontana:~$ tr '\0' ' ' < /proc/$PID/cmdline; echo
/opt/tramontana/app/executable --config /etc/tramontana/app.conf
operator@srv-tramontana:~$ sudo ls -l /proc/$PID/cwd /proc/$PID/exe
lrwxrwxrwx 1 svc-tramontana tramontana 0 Aug 18 12:52 /proc/1284/cwd -> /opt/tramontana/app
lrwxrwxrwx 1 svc-tramontana tramontana 0 Aug 18 12:52 /proc/1284/exe -> /opt/tramontana/releases/3.2.1/executable
operator@srv-tramontana:~$ sudo tr '\0' '\n' < /proc/$PID/environ | grep -E 'PATH|LANG'
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=C.UTF-8

cmdline stores the arguments separated by null bytes, hence the tr. It confirms that the app reads /etc/tramontana/app.conf, so a change there affects it. The cwd is /opt/tramontana/app, the symbolic link: it means its relative paths are resolved through it.

The revealing piece of information is exe, which points to /opt/tramontana/releases/3.2.1/executable, the already-resolved path. It is the proof that the atomic deployment works as we expected: the kernel fixed the real binary at start-up, so changing the app link to another release does not affect the running process. You know which version is genuinely being served, not which one the link says right now. And environ shows a minimal PATH and LANG=C.UTF-8: the service does not inherit your interactive environment, something you already knew from 03-01 and which will come up again in the next lesson.

Solution 2.

operator@srv-tramontana:~$ tar -czf /tmp/rel.tar.gz /opt/tramontana/releases &
[1] 2311
operator@srv-tramontana:~$ fg %1
tar -czf /tmp/rel.tar.gz /opt/tramontana/releases
^Z
[1]+  Stopped                 tar -czf /tmp/rel.tar.gz /opt/tramontana/releases
operator@srv-tramontana:~$ ps -o pid,stat -p 2311
    PID STAT
   2311 T
operator@srv-tramontana:~$ bg %1
[1]+ tar -czf /tmp/rel.tar.gz /opt/tramontana/releases &
operator@srv-tramontana:~$ renice -n 15 -p 2311 && ionice -c 3 -p 2311
2311 (process ID) old priority 0, new priority 15
operator@srv-tramontana:~$ disown -h %1
operator@srv-tramontana:~$ ps -o pid,ni,stat -p 2311
    PID  NI STAT
   2311  15 SN

Each check contributes something: STAT at T confirms that Ctrl+Z really did stop it (SIGSTOP), and after bg and the adjustments, SN indicates that it is sleeping at low priority (N) and NI is 15. The renice to 15 did not need sudo because raising your own nice value is permitted to any user; lowering it would require privileges. disown -h does not remove it from the job list, but it marks it as not to receive SIGHUP when the session closes. If we had known in advance that the task was a long one, the clean approach would have been nohup ... & from the start.

Solution 3.

operator@srv-tramontana:~$ sudo lsof -i :8080 -sTCP:LISTEN
COMMAND     PID           USER   FD   TYPE  DEVICE NODE NAME
executable 1284 svc-tramontana   7u  IPv4 1284091  TCP *:http-alt (LISTEN)
operator@srv-tramontana:~$ ps -o pid,user,etime,cmd -p 1284
    PID USER         ELAPSED CMD
   1284 svc-tram    04:31:12 /opt/tramontana/app/executable --config /etc/tramontana/app.conf

We now have the three answers: it is occupied by executable, it belongs to svc-tramontana and it has been running for four and a half hours. -sTCP:LISTEN filters the listening sockets and discards the established connections, which would be noise.

operator@srv-tramontana:~$ sudo kill -TERM 1284
operator@srv-tramontana:~$ sleep 10; pgrep -x executable || echo 'terminated correctly'
terminated correctly
operator@srv-tramontana:~$ sudo lsof -i :8080
operator@srv-tramontana:~$ echo "port free: $?"
port free: 1

Two different verifications and both necessary. pgrep confirms that the process no longer exists; lsof with no output confirms that the port is free, which is not exactly the same thing: a socket can stay in TIME_WAIT for a few seconds after the process closes, and trying to start the new version in that gap would fail for a different and baffling reason. Checking the resource you actually need, and not just the process, is what avoids that mistaken diagnosis. You will see the TIME_WAIT state and the rest of the TCP states in 03-08.

Conclusion

You have stopped looking at files and started looking at what is running, and with that you have resolved a real incident from start to finish.

  • You know what the kernel keeps about each process — PID, PPID, effective UID, state, priority, VSZ and RSS — and that the memory figure that matters is RSS.
  • You walk the process tree with pstree from systemd (PID 1) and you understand the fork / exec / wait / exit cycle, which is where the redirection from 03-04 fits in.
  • You distinguish a zombie from an orphan: a zombie cannot be killed and you act on the parent; an orphan is adopted by systemd and is not a problem.
  • You know the states R, S, D, T, Z and why a process in D responds to no signal, not even to -9.
  • You read ps aux and ps -ef column by column, you build views with ps -eo ... --sort=, and you know that ps's %CPU is an average since start-up.
  • You interpret top's header: the load average against the number of cores and its trend, the us/sy/ni/id/wa/hi/si/st breakdown — with wa as the sign that the bottleneck is the disk — and the fact that buff/cache is not lost memory.
  • You use pgrep/pkill instead of ps | grep | kill, always checking before killing, and you apply the professional rule for signals: SIGTERM, wait, verify and only then SIGKILL, knowing what you lose with -9.
  • You adjust priorities with nice, renice and ionice, choosing the lever according to where the bottleneck is.
  • You control jobs with &, jobs, fg, bg, Ctrl+Z, disown and nohup, and you are clear that none of it is any use for a production service.
  • You answer "who has this open?" and "who is occupying this port?" with lsof and fuser, and you go to /proc/<pid>/ when you want the truth with no intermediaries.

The diagnosis has left a very specific outstanding task: the nightly backup runs at the wrong time, with the wrong priority and over more data than necessary. Fixing it is the subject of the next lesson. Scheduling Tasks with Cron will teach you the syntax of the five fields down to its most convoluted cases, the three classic failures that cause 90% of the incidents — the minimal PATH, the output email nobody reads and the time zone — how to debug a task that "works by hand and not in cron", and how to stop two runs overlapping with flock, which is precisely what may have happened in the early hours this morning.

Linux Course: From Beginner to System Administrator

Module 1: Introduction to Linux

Module 2: Basic Linux Commands

Module 3: Advanced Command-Line Skills

Module 4: Shell Scripting

Module 5: System Administration

Module 6: Networking and Security

Module 7: Advanced Topics

Module 8: Practical Projects

© Copyright 2026. All rights reserved