---
id: linux-process-management
title: "Process và systemd"
track: linux
trackTitle: "Linux Basics & Shell Scripting"
order: 4
level: core
status: published
estimatedMinutes: 35
prerequisites:
  - linux-filesystem-permissions
  - linux-selinux
next: linux-shell-scripting
reviewedAgainst: 2026-10-15
sources:
  - title: "KodeKloud Engineer — 100 Days of DevOps"
    url: https://engineer.kodekloud.com/practice
  - title: "signal(7) — Linux man-pages"
    url: https://man7.org/linux/man-pages/man7/signal.7.html
  - title: "systemctl — systemd"
    url: https://www.freedesktop.org/software/systemd/man/latest/systemctl.html
  - title: "journalctl — systemd"
    url: https://www.freedesktop.org/software/systemd/man/latest/journalctl.html
---

# Process và systemd

`systemctl` và `journalctl` chỉ có trên host chạy systemd như Ubuntu, Debian, Fedora, RHEL. Container tối giản thường không có systemd.

## Để làm gì

Bạn cần biết process nào đang chạy, process cha của nó là gì, và dừng nó sao cho app còn kịp đóng connection. Với service cài trên host, bạn start, stop và xem log qua systemd. Job theo giờ thì viết crontab của user, năm trường thời gian rồi tới lệnh.

## Gồm những gì

- PID, PPID, và trạng thái process.
- `ps aux`, `ps -ef`, `ps -p PID -o`.
- `kill` không argument thì `SIGTERM`. `kill -s SIGKILL` hoặc `kill -9`.
- `systemctl status`, `reload`, `restart`. `journalctl -u UNIT -n N --no-pager`.
- Crontab của user: năm trường thời gian, rồi lệnh. `crontab -l`, `-e`, `-r`.

`htop` có giao diện dễ nhìn hơn nhưng không có sẵn trên mọi image, nên bài này chỉ dùng tool trong bộ cơ bản của Linux.

## Dùng khi nào

Bạn dùng các lệnh này khi cần biết một service còn sống và đang nhận request không, process nào đang ngốn CPU, hoặc khi vừa sửa config và cần khởi động lại đúng unit.

Trong container, process chính là PID 1 bên trong namespace của container. Các lệnh trong bài xem process của host hoặc của shell hiện tại, không thay được `kubectl logs`.

## Kiến thức cốt lõi

Mỗi process có một PID, còn PPID là PID của process cha. Khi shell chạy một lệnh, shell là cha và lệnh là con.

`ps aux` là cú pháp kiểu BSD: liệt kê mọi process, kèm user và cột command. `ps -ef` là kiểu POSIX, cũng liệt kê mọi process nhưng bộ cột khác. Cả hai đều đúng; trong script, bạn chọn một kiểu và đọc đúng cột của kiểu đó.

`top` cập nhật CPU và memory theo chu kỳ. Trong `top`, phím `M` sắp xếp theo memory, phím `P` theo CPU, và `q` để thoát.

Signal là thông báo gửi tới process. Số chuẩn nằm trong `signal(7)`:

| Signal | Số | Ý nghĩa vận hành |
| --- | --- | --- |
| `SIGTERM` | 15 | Yêu cầu thoát. Process có thể bắt signal này để đóng file và connection. Đây là signal mặc định của `kill`. |
| `SIGKILL` | 9 | Kernel thu hồi process. Process không bắt được signal này và không chạy cleanup. |
| `SIGHUP` | 1 | Nhiều daemon dùng signal này để nạp lại cấu hình. |

Quy tắc: gửi `SIGTERM` trước và chờ process thoát. Chỉ khi nó không chịu thoát mới dùng `SIGKILL`.

systemd quản lý các unit, và một service thường được khai báo trong file `*.service`. `systemctl status nginx` cho biết unit có active không, PID chính là gì, kèm vài dòng log gần nhất.

`restart` dừng rồi chạy lại process, nên service bị ngắt một khoảng ngắn. `reload` bảo process nạp lại config, với điều kiện unit hỗ trợ. Với Nginx, chạy `nginx -t` thấy ok rồi mới `reload`.

Cron chạy một lệnh theo lịch, dưới user sở hữu crontab đó. Một dòng trong crontab của user có năm trường rồi tới lệnh:

| Trường | Giá trị |
| --- | --- |
| phút | 0–59 |
| giờ | 0–23 |
| ngày trong tháng | 1–31 |
| tháng | 1–12 |
| ngày trong tuần | 0–7, 0 và 7 đều là Chủ nhật |

`*` là mọi giá trị của trường đó. `15 2 * * *` là 02:15 mỗi ngày. Cả ngày-trong-tháng và ngày-trong-tuần đều khác `*` thì lệnh chạy khi một trong hai trường khớp, không phải khi cả hai cùng khớp.

`/etc/crontab` và file trong `/etc/cron.d/` có thêm một trường username đứng trước lệnh. Crontab cài bằng lệnh `crontab` không có trường đó. Daemon đặt `SHELL` thành `/bin/sh` nếu dòng crontab không ghi đè. Ký tự `%` trong lệnh thành newline, phần sau `%` đi vào stdin, trừ khi viết `\%`.

`crontab file` thay cả bảng của user bằng nội dung file. `crontab -l` in bảng hiện tại. `crontab -e` mở editor trong `VISUAL` hoặc `EDITOR`, thoát editor thì bảng mới được cài. `crontab -r` xóa cả bảng, không xóa một dòng. `crontab -u user` sửa bảng của user khác và cần root.

## Lệnh và code mẫu

Lab chỉ tạo một process `sleep` thuộc user hiện tại:

```bash
sleep 300 &
lab_pid=$!
ps -p "$lab_pid" -o pid,ppid,stat,cmd
kill "$lab_pid"
wait "$lab_pid" || true
```

`$!` là PID của process nền vừa tạo. Nếu không ghi tên signal, `kill` gửi `SIGTERM`.

Xem service trên host có systemd:

```bash
systemctl status nginx
sudo systemctl reload nginx
journalctl -u nginx -n 50 --no-pager
```

`status` chỉ đọc thông tin. `reload` tác động lên process đang chạy nên cần quyền phù hợp. `--no-pager` in log thẳng ra terminal thay vì mở pager, tiện khi dùng trong script.

`ps -p` nhận PID. `-o` nhận danh sách cột. `kill -s` nhận tên signal, argument cuối là PID.

Cron lab chỉ cài khi `crontab -l` báo user này chưa có bảng. `crontab file` thay cả bảng đang có.

```bash
mkdir -p "$HOME/devops-lab/linux"
printf '%s\n' '15 2 * * * date >> "$HOME/devops-lab/linux/cron.log"' > "$HOME/devops-lab/linux/lab.cron"
crontab "$HOME/devops-lab/linux/lab.cron"
crontab -l
crontab -r
```

Dòng trong file giữ nguyên `$HOME`. Khi job chạy, `/bin/sh` của cron mới khai triển biến đó. `crontab -r` ở cuối chỉ an toàn khi bảng vừa cài là bảng duy nhất của user.

## Thực hành

1. Chạy khối `sleep` ở trên.
2. Trong lúc `sleep` còn sống, mở terminal thứ hai và chạy `ps -p <PID> -o pid,ppid,stat,cmd`.
3. Đọc cột `STAT`: `S` là process đang ngủ chờ event, `R` là đang chạy hoặc sẵn sàng chạy.
4. Gửi `kill`, rồi dùng `ps -p "$lab_pid"` xác nhận process đó đã biến mất.
5. Nếu máy lab có Nginx, chạy `systemctl status nginx` và đọc các dòng `Active`, `Main PID`, đường dẫn unit.
6. Nếu `crontab -l` báo chưa có bảng, cài `lab.cron`, đọc `crontab -l`, rồi `crontab -r`. Bảng đã có dòng khác thì dừng trước `crontab file`.

## Kết quả mong đợi

- `ps` in một dòng `sleep 300`, PPID là shell hiện tại.
- Sau `kill`, process không còn.
- `systemctl status`, nếu unit có thật, exit code `0` khi service đang chạy và khác `0` khi đang dừng.
- `crontab -l` in đúng một dòng `15 2 * * *`, không có username. Sau `crontab -r`, `crontab -l` báo không còn bảng.

## Checklist

- [ ] Đọc được PID và PPID.
- [ ] Nói được vì sao gửi `SIGTERM` trước `SIGKILL`.
- [ ] Chọn `reload` hay `restart` dựa trên việc connection đang mở có được phép đứt không.
- [ ] Lấy log của một unit bằng `journalctl -u`.
- [ ] Viết crontab của user với năm trường thời gian, không chèn username.
- [ ] Xóa một job bằng cách sửa bảng. `crontab -r` xóa cả bảng.

## Thẻ ôn

### linux-process-management-signal
kind: command
cue: Cần dừng process thì gửi SIGTERM trước để nó kịp đóng socket và ghi dữ liệu. SIGKILL chỉ khi nó không chịu thoát.

Q: Một process ứng dụng cần dừng, và trên mạng có người bảo `kill -9` cho chắc. Signal nào nên gửi trước, process được làm gì trong lúc đó, và khi nào mới dùng SIGKILL?

A: Gửi SIGTERM trước. Process còn cơ hội bắt signal, đóng socket, ghi nốt dữ liệu rồi tự thoát. Sau đó `ps` lại để xem nó đã biến chưa. SIGKILL không thể bắt: kernel kết liễu ngay, buffer chưa ghi có thể mất, tiến trình con có thể thành mồ côi. Chỉ dùng SIGKILL khi đã gửi SIGTERM mà process không thoát. `kill -9` không phải bước đầu.

### linux-process-management-reload
kind: decide
cue: Sửa config Nginx thì `nginx -t` trước. Config đúng và đang có connection thì `reload`, đừng `restart` một file đang sai.

Q: Bạn vừa sửa config Nginx trong lúc vẫn còn connection của user. Nên `systemctl reload` hay `restart`? Nếu file config đang sai thì `restart` rủi ro gì?

A: Chạy `nginx -t` trước để Nginx tự kiểm tra cú pháp file mới. Config đúng thì `systemctl reload nginx`: master process giữ service, worker nhận config mới, connection đang mở không bị đứt như một lần dừng hẳn. `restart` dừng service rồi start lại, khoảng đó request mới không được phục vụ. Đừng `restart` khi `nginx -t` còn báo lỗi. Service dừng xong có thể không lên lại được vì file config không nạp nổi, và bạn mất cả bản đang chạy.

### linux-process-management-pid
kind: distinguish
cue: PID là số của chính process. PPID là số của process cha. Shell chạy một lệnh thì shell là cha, lệnh là con.

Q: `ps` in hai cột PID và PPID. Bạn thấy một lệnh mình vừa gõ có PPID trùng PID của shell. Hai số đó nghĩa là gì?

A: PID là số kernel gán cho chính process đó. PPID là PID của process đã tạo ra nó, tức process cha. Khi shell chạy một lệnh, shell là cha và lệnh là con, nên PPID của lệnh chính là PID của shell. Nhìn PPID để biết process này được ai gọi lên, không chỉ biết nó tên gì. Giết nhầm cha có thể kéo theo cả nhóm con đang chạy dưới nó.

### linux-process-management-journal
kind: command
cue: Log của một unit systemd lấy bằng `journalctl -u`. `systemctl status` chỉ vài dòng cuối, không thay một khoảng thời gian bạn chỉ định.

Q: Một service do systemd quản lý vừa lỗi. `systemctl status` hiện vài dòng rồi hết. Đủ để xem chuyện xảy ra trong một giờ qua chưa, và lệnh nào mới đúng?

A: Chưa đủ. `systemctl status` tóm tắt unit và nhét vài dòng log cuối cho dễ nhìn, không phải cách lấy log theo khoảng thời gian. `journalctl -u` kèm tên unit mới đọc journal của đúng service đó. Thêm mốc thời gian khi bạn cần một đoạn, không phải vài dòng cuối màn hình. Status để biết unit đang chạy hay failed. Journal để đọc chuyện đã xảy ra.

### linux-process-management-cron
kind: trap
cue: Crontab của user có năm trường rồi tới lệnh. `crontab file` thay cả bảng. `crontab -r` xóa cả bảng, không xóa một dòng.

Q: Bạn cần job lúc 02:15 mỗi ngày dưới user của mình. Copy một dòng từ `/etc/crontab`, dòng đó có username đứng trước lệnh, rồi `crontab -r` để gỡ job thử. Hai chỗ đó hỏng thế nào?

A: Crontab cài bằng lệnh `crontab` không có trường username. Năm trường là phút, giờ, ngày, tháng, thứ. Username chỉ có trong `/etc/crontab` và `/etc/cron.d/`. Chép nguyên dòng system vào bảng user thì trường username bị đọc thành lệnh. `crontab -r` không gỡ một dòng: nó xóa cả bảng của user. Sửa bằng `crontab -e`, hoặc cài lại từ file chỉ khi bảng đó là bảng lab.

## Lỗi thường gặp

**Dùng `kill -9` ngay từ đầu.** Process không kịp ghi dữ liệu và đóng socket. Hãy gửi `SIGTERM`, kiểm tra lại bằng `ps`, và chỉ khi process không chịu thoát mới dùng `SIGKILL`.

**`systemctl restart` khi file config đang sai.** Service có thể không lên lại được. Với Nginx, chạy `nginx -t` trước, rồi mới `systemctl reload nginx`.

**Cài `htop` vào mọi container chỉ để xem process.** Trên host, `ps` và `top` đã đủ để debug. Image container nên giữ nhỏ; tool debug có thể chạy tạm khi cần, nhưng đừng đưa vào image production.

**Chép dòng `/etc/crontab` vào `crontab -e`.** Dòng system có username. Bảng của user không có trường đó, nên giờ chạy và lệnh đều lệch. Viết năm trường rồi tới lệnh.

**`crontab -r` để xóa một job.** Lệnh này xóa cả bảng. Job khác của cùng user mất theo.

## Nguồn

1. [KodeKloud Engineer — 100 Days of DevOps](https://engineer.kodekloud.com/practice) — đề cương ngày cron và ngày xử lý process. Trang practice cần đăng nhập. Bài này viết lại lệnh, không chép lab.
2. [ps(1)](https://man7.org/linux/man-pages/man1/ps.1.html) và [top(1)](https://man7.org/linux/man-pages/man1/top.1.html) — Linux man-pages.
3. [signal(7)](https://man7.org/linux/man-pages/man7/signal.7.html) — danh sách signal và hành vi mặc định.
4. [kill(1)](https://man7.org/linux/man-pages/man1/kill.1.html) — gửi signal theo PID.
5. [systemctl](https://www.freedesktop.org/software/systemd/man/latest/systemctl.html) và [journalctl](https://www.freedesktop.org/software/systemd/man/latest/journalctl.html) — systemd.
6. [crontab(1)](https://man7.org/linux/man-pages/man1/crontab.1.html) và [crontab(5)](https://man7.org/linux/man-pages/man5/crontab.5.html) — `-l`, `-e`, `-r`, và năm trường thời gian.

## Tiếp theo

[Shell scripting](./03-shell-scripting.md)
