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
- What a process is
- The process tree
- Life cycle: fork, exec, wait, exit
- Zombies and orphans
- Process states
psproperlytopandhtop: reading the headerpgrepandpkill- Signals
- Priorities:
nice,reniceandionice - Job control in the interactive session
lsofandfuser- The Tramontana case: the app hanging in the early hours
- 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.
- 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.
- 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.
- 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.
- 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.
ps properly
ps properlyps 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.
top and htop: reading the header
top and htop: reading the headertop - 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:
- It is not a percentage. It has to be compared with the number of cores: on the
srv-tramontanaVM, with 2 vCPUs, a load of 2.00 is full occupancy and 3.42 means there is more work than the machine can handle. - 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.
pgrep and pkill
pgrep and pkilloperator@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'
- 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
0Ten 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.
- Priorities:
nice, renice and ionice
nice, renice and ioniceThe 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/datanice 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.
- 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.
lsof and fuser
lsof and fuserlsof ("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.gzTwo 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.
- 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.1State 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/executableThere 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 19Both 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 -9as 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%CPUas an instantaneous value. It is an average since start-up. Usetop. - Being alarmed by low free memory.
buff/cacheis reusable cache. Look atavail Mem. - Comparing the load average with 100. Compare it with the number of cores, and remember it includes I/O waiting.
ps | grep | killon a short name.grepitself shows up in the list and a partial match kills too much. Usepgrep -aand thenpkill.- 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'swafirst. If it is high, the bottleneck is the disk and half the hypotheses are ruled out at a stroke. - Tip:
ps -eoaccepts 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-8cmdline 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 SNEach 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.confWe 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: 1Two 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
pstreefromsystemd(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
systemdand is not a problem. - You know the states R, S, D, T, Z and why a process in
Dresponds to no signal, not even to-9. - You read
ps auxandps -efcolumn by column, you build views withps -eo ... --sort=, and you know thatps's%CPUis an average since start-up. - You interpret
top's header: the load average against the number of cores and its trend, theus/sy/ni/id/wa/hi/si/stbreakdown — withwaas the sign that the bottleneck is the disk — and the fact thatbuff/cacheis not lost memory. - You use
pgrep/pkillinstead ofps | 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,reniceandionice, choosing the lever according to where the bottleneck is. - You control jobs with
&,jobs,fg,bg,Ctrl+Z,disownandnohup, 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
lsofandfuser, 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
- What Is Linux?
- History of Linux
- Linux Distributions
- Installing Linux
- First Contact with the System
- The Linux File System Structure
Module 2: Basic Linux Commands
- Introduction to the Command Line
- Getting Help and System Documentation
- Navigating the File System
- File and Directory Operations
- Viewing and Editing Files
- Hard and Symbolic Links
- File Permissions and Ownership
Module 3: Advanced Command-Line Skills
- The Shell Environment: Variables, Aliases and History
- Using Wildcards and Regular Expressions
- Searching Files and Content: find, locate and grep
- Pipes and Redirection
- Text Processing: cut, sort, uniq, sed and awk
- Process Management
- Scheduling Tasks with Cron
- Networking Commands
Module 4: Shell Scripting
- Introduction to Shell Scripting
- Variables and Data Types
- Script Input, Output and Arguments
- Control Structures
- Functions and Libraries
- Debugging and Error Handling
- Production Scripts: Best Practices
Module 5: System Administration
- User and Group Management
- sudo and Special Permissions
- Package Management
- Disk Management
- systemd and Service Management
- System Logs: journald and syslog
- System Monitoring and Performance Tuning
- Backup and Restore
Module 6: Networking and Security
- Network Configuration
- SSH and Remote Access
- Firewalls and Perimeter Security
- Intrusion Detection Systems
- Secrets Management and TLS Certificates
- Securing Linux Systems
Module 7: Advanced Topics
- The Boot Process and System Recovery
- Advanced Diagnostics: strace, perf and eBPF
- Linux Kernel Tuning
- Virtualization with Linux
- Linux Containers and Docker
- Automation with Ansible
- High Availability and Load Balancing
