Mở đầu

Trong quá trình vận hành các service trên Linux, chắc hẳn ít nhiều các bạn từng nhìn thấy một số config liên quan tới file .pid của các service đó. Nếu bạn nào tò mò cat nội dung file đó ra thì sẽ thấy file này chỉ chứa duy nhất process id (pid) của tiến trình tương ứng.

ls /var/run | grep pid
cat /var/run/docker.pid; echo
ps aux | grep dockerd | grep -v grep

Trong ví dụ trên docker daemon có pid là 1975323.

Vậy tại sao một service trong lại phải ghi process id của nó ra file?

Tác dụng của .pid file

Theo mình hiểu thì đây là một cơ chế giúp hai hay nhiều process nhận diện / tương tác với nhau.

Thông thường, khi một process của serviceA start, nó lưu pid của chính mình vào một file, nhằm mục đích nói cho các process khác1 biết là:

serviceA đang|đã start một process có id như sau, các cậu hãy căn cứ vào đó để cân nhắc hành động tiếp theo.

Các hành động tiếp theo ở đây có thể là:

  • Không start thêm process mới của serviceA nữa, để đảm bảo tại một thời điểm sẽ chỉ có một process serviceA chạy.
  • Để một serviceB khác có thể gửi Signal tương tác với serviceA đang chạy: có thể đơn thuần thông báo cho serviceA một điều gì đó hoặc kill luôn serviceA nếu cần.
    • Việc sử dụng .pid file thay vì tìm process id theo tên process, giúp serviceB tương tác đúng với process mà nó quan tâm; vì nhiều khả năng serviceA có thể chạy nhiều process tại một thời điểm. (Xem ví dụ bên dưới về httpd child process)
  • Đôi khi .pid chỉ được dùng cho mục đích giám sát & cảnh báo, xem serviceA còn đang hoạt động không.
HTTPD fork ra nhiều child process, mỗi process có một id khác nhau
file httpd.pid chứa pid của parent process, đảm bảo các process khác tương tác với HTTPD thông qua parent process thay vì child process cùng tên.
service HTTPD (apache) webserver đã start một process có id 1 (trong docker container)
ghi pid 1 vào /opt/bitnami/apache/var/run/httpd.pid
khi start một process httpd mới, process này sẽ thấy file httpd.pid trên tại và báo lỗi

Tuy nhiên, việc file .pid của một service tồn tại, không đảm bảo rằng service đó đang hoạt động. Trong nhiều trường hợp, khi một service bị chết bất đắc kỳ tử (không kịp dọn dẹp mọi thứ – bao gồm cả .pid file – trước khi chết | bị giết) thì file .pid vẫn sẽ ở đó. Như vậy, file .pid tồn tại, đồng nghĩa với một trong hai khả năng:

  • Hoặc process đó đã không ra đi thanh thản (ungraceful shutdown).
  • Hoặc process đó đang chạy.

Trường hợp (1), các file .pid ‘rác’, sẽ làm các process khác ‘hiểu’ sai về trạng thái của process được đề cập (đã dừng nhưng vẫn có file .pid), vì vậy file .pid nên được xử lý cẩn thận trong quá trình start/stop process để tránh điều đó xảy ra. Bạn đọc có thể xem cụ thể ở phần phía dưới của bài viết.

Nhưng nếu .pid không hoàn toàn tin cậy, tại sao nó lại phổ biến như thế? Đó là vì tính đơn giản của giải pháp này: chỉ cần ghi pid ra một file, các process khác đọc file đó, điều này không tốn nhiều tài nguyên hệ thống.

.pid thường lưu ở đâu?

Về lý thuyết, các file .pid này có thể được lưu ở bất cứ đâu, miễn sao các process khác biết được đường dẫn của nó. Với các process trong cùng một service, path của .pid file thường được chỉ định trong configuration của service.

Tuy nhiên, giống như file configuration và các file khác trên linux, tuy không bắt buộc, các service đa phần vẫn follow theo các convention nhất định về việc tổ chức file. PID file thường được lưu ở một trong những đường dẫn sau tuỳ trường hợp:

  • /var/run/
  • /etc/init.d/ – SysV Init, /etc/systemd/ – SystemD, /etc/init/ – Upstart

Ví dụ trường hợp của httpd web server, bạn có thể tìm setting này ở/etc/httpd/conf/httpd.conf.

cat /etc/httpd/conf/httpd.conf | grep -i pid
# PidFile "/var/run/httpd.pid"

Mỗi khi có một process httpd mới được start, nó sẽ đọc httpd.conf để biết đường dẫn file .pid, rồi check xem file pid đó (/var/run/httpd.pid) có tồn tại không, nếu có process mới sẽ báo lỗi httpd (pid XXX) already running và thoát.

Một số ví dụ thực tế

Đảm bảo chỉ một process của service/tool chạy tại một thời điểm

Bash script trong ví dụ phía dưới làm nhiệm vụ backup MySQL database. Trước khi thực hiện việc backup, script này sẽ kiểm tra xem script này có đang được chạy không, nhằm đảm bảo không có hai job backup được chạy cùng một thời điểm.

#!/bin/bash

# chỉ định đường dẫn tới pidfile
PID_FILE="/var/run/mysql_backup.pid"

# Function check xem process có pid ghi trong pid file có đang chạy không
# thực ra function này nên lấy process id từ tên process (ví dụ mysql_backup.bash),
# rồi đối chiếu với pid trong .pid file
is_running() {
	ps -p "$1" >/dev/null 2>&1
}

# kiểm tra sự tồn tại của pid file
if [ -f "$PID_FILE" ]; then
	# đọc pid từ pid file
	PID=$(cat "$PID_FILE")

	if is_running "$PID"; then
		echo "Backup script is already running with PID $PID."
		echo "Please wait for the current backup to finish before starting a new one."
		exit 1
	else
		echo "Found stale PID file. Removing it."
		rm -f "$PID_FILE"
	fi
fi

# ghi process id hiện tại ra pid pid file
# `$$` trong bash được dùng để in ra process id của process hiện tại
echo $$ >"$PID_FILE"

# khi nhận được các signal INT TERM EXIT thì clean pid_file và exit process
# phòng trường hợp process bị kill giữa chừng
trap "rm -f '$PID_FILE'; exit" INT TERM EXIT

MYSQL_USER="your_username"
MYSQL_PASSWORD="your_password"
MYSQL_DATABASE="your_database"
BACKUP_DIR="/path/to/backup"
TIMESTAMP=$(date +"%F-%H-%M-%S")
BACKUP_FILE="$BACKUP_DIR/$MYSQL_DATABASE-$TIMESTAMP.sql"

echo "Starting MySQL backup..."
mysqldump -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE" >"$BACKUP_FILE"

if [ $? -eq 0 ]; then
	echo "Backup completed successfully: $BACKUP_FILE"
else
	echo "Backup failed!"
fi

# xoá pid file khi đã hoàn thành việc backup
rm -f "$PID_FILE"

Lệnh trap ở dòng 34, dùng để xoá pid file trước khi process exit – khi nhận được các signal: TERM, INT, EXIT.

Tuy nhiên trong Linux, có một signal chúng ta không trap được, đó là SIGKILL (được dùng để force kill một process, có thể được gửi bởi người dùng hoặc hệ thống – khi bị OOM, v.v.). Điều này đồng nghĩa với việc .pid file sẽ không được xoá một cách đúng đắn. Đó là lý do vì sao ngoài việc kiểm tra nội dung pidfile, chúng ta còn cần đối chiếu trong hàm is_running.

Apache Tomcat – initd

Đây là một script implement các function start, stop, v.v. để quản lý process Tomcat thông qua initd:

https://gist.github.com/chetan/7946650

Như chúng ta có thể thấy, trước khi start một process mới, init.d sẽ kiểm tra xem đã có process tomcat nào được start trước đó chưa.

Trong hàm start(), lệnh kill -0 được dùng để kiểm tra xem có process nào có pid trong catalina.pid đang chạy không2.

Thực chất hàm này lại gọi tới catalina.sh, quá trình check .pid file lại được lặp lại. Bạn nào tò mò có thể đọc thêm cách catalina.sh implement việc start/stop/force start/force stop tomcat process, đa phần các action này đều có liên quan tới .pid file của tomcat.

Với systemd, việc quản lý service có thể được thực hiện thông qua cgroup, trong một cgroup cũng sẽ có thông tin pid của các process thuộc về nó. Tất nhiên điều này không ngăn cản việc một service quản lý process của mình thông qua .pid file đã trình bày phía trên.

Nguồn tham khảo

ChatGPT
https://superuser.com/questions/454449/linux-alternative-places-where-to-store-pid-file-instead-of-var-run
https://gist.github.com/drmalex07/e6e99dad070a78d5dab24ff3ae032ed1
https://unix.stackexchange.com/questions/12815/what-are-pid-and-lock-files-for
https://www.tutorialspoint.com/what-is-a-pid-file-in-linux
https://stackoverflow.com/questions/47826123/what-is-systemd-pid-file

Ghi chú

  1. Process khác ở đây có thể là process của chính service đó, hoặc của một service khác (thường là các service | tool làm nhiệm vụ service management) ↩︎
  2. Lệnh này không gửi signal đến process tomcat, mà chỉ check xem process tomcat có tồn tại không, và nếu có, script hiện tại – init.d – có permission can thiệp vào process tomcat không ↩︎

Categorized in:

Tagged in:

,