0
0
Lập trình
Harry Tran
Harry Tran106580903228332612117

Linux: Gửi sự kiện tùy chỉnh với tín hiệu kill

Đăng vào 5 ngày trước

• 5 phút đọc

Giới thiệu

Trong phát triển phần mềm trên Linux, có lúc chúng ta cần một tiến trình daemon phản hồi với các sự kiện bên ngoài. Việc thông báo cho một tiến trình đang chạy về một sự kiện bên ngoài và yêu cầu nó thực hiện một công việc tương ứng là điều thường gặp. Thay vì sử dụng giao tiếp giữa các tiến trình (IPC) như socket, pipe hay dbus, chúng ta có thể sử dụng tín hiệu hệ điều hành để đạt được điều này một cách hiệu quả hơn.

Tín hiệu thực thời

Linux đã cung cấp chức năng cho phép chúng ta gửi nhiều loại tín hiệu đến các tiến trình đang chạy. Chúng ta thường sử dụng tín hiệu để kết thúc hay khởi động lại một tiến trình. Tuy nhiên, Linux cũng có một tập hợp tín hiệu đặc biệt gọi là tín hiệu thực thời, cho phép các daemon phản hồi các sự kiện tùy ý. Những tín hiệu này không có ý nghĩa gì với kernel hoặc tiến trình tự chúng, mà cách định nghĩa và hành vi của chúng được để cho các tiến trình/daemon tự quyết định.

Số lượng tín hiệu thực thời

Linux hỗ trợ tổng cộng 33 tín hiệu thực thời, được đánh số từ 32 đến 64. Trong thực tế, việc sử dụng tín hiệu thực thời có thể khác nhau tùy thuộc vào cách triển khai luồng của glibc. Do đó, mức độ tín hiệu có thể không bắt đầu từ 32. Cách tốt nhất để sử dụng những tín hiệu này là dùng ký hiệu SIGRTMIN+n hoặc SIGRTMAX-n, với n bắt đầu từ 1. Chúng ta nên đảm bảo rằng giá trị của các tín hiệu này không vượt quá SIGRTMIN - SIGRTMAX. Hai giá trị này được xác định bởi kernel và người dùng không cần lo lắng về giá trị cụ thể của chúng.

Liệt kê tín hiệu trong bash

Chúng ta có thể liệt kê tất cả các tín hiệu có sẵn trong bash như sau:

Copy
bash -c "kill -l"

Tất cả các tín hiệu bắt đầu bằng SIGRTMIN hoặc SIGRTMAX là các tín hiệu thực thời và giá trị số tương ứng của chúng sẽ được hiển thị phía trước.

Sử dụng tín hiệu thực thời trong ứng dụng

Trong phần này, tôi sẽ trình diễn cách sử dụng các tín hiệu này bằng một script bash đơn giản, nhưng cách tiếp cận tương tự có thể áp dụng cho bất kỳ ngôn ngữ lập trình nào. Đầu tiên, chúng ta cần quyết định các tín hiệu thực thời mà ứng dụng của chúng ta sẽ sử dụng và chức năng mà các tín hiệu đó sẽ tương ứng. Sau đó, chúng ta cần thực hiện hai bước sau trong chương trình của mình cho mỗi tín hiệu thực thời đã quyết định:

  1. Tùy thuộc vào chức năng tín hiệu mà chúng ta đã quyết định, định nghĩa hàm xử lý riêng cho mỗi tín hiệu.
  2. Gán các hàm xử lý này cho các tín hiệu thực thời tương ứng mà chúng ta đã quyết định.

Ví dụ chương trình

bash Copy
#!/usr/bin/env bash

# Phần 1: định nghĩa tất cả các hàm xử lý

send_email_notification() {
  echo "Đang gửi thông báo qua email"
  # Mã gửi email sẽ được thêm vào đây
}

enable_debug_logs() {
  set -x
  echo "Bật ghi nhật ký debug"
}

disable_debug_logs() {
  set +x
  echo "Tắt ghi nhật ký debug"
}

# Phần 2: thêm hàm xử lý vào tín hiệu
trap send_email_notification 'SIGRTMIN+1'
trap enable_debug_logs 'SIGRTMIN+2'
trap disable_debug_logs 'SIGRTMIN+3'

# Vòng lặp chính thực hiện công việc
while true; do
  echo "Đang thực hiện công việc"
  sleep 10
done

Chương trình bash đơn giản này được chia thành hai phần. Các tín hiệu thực thời và chức năng mà chương trình sẽ sử dụng như sau:

  • SIGRTMIN+1: gửi thông báo email khi được yêu cầu
  • SIGRTMIN+2: bật in ra nhật ký debug
  • SIGRTMIN+3: tắt in ra nhật ký debug

Phần 1: định nghĩa các hàm xử lý

Chúng ta định nghĩa ba hàm xử lý:

  • Hàm gửi thông báo email (send_email_notification): Hàm này sẽ gửi thông báo email đến một tập hợp địa chỉ email khi tín hiệu được nhận.
  • Bật nhật ký debug (enable_debug_logs): Hàm này sẽ chạy set -x để in ra các dòng mà script đang thực hiện.
  • Tắt nhật ký debug (disable_debug_logs): Hàm này sẽ chạy set +x để dừng việc in ra các dòng mà script đang thực hiện.

Phần 2: gán các hàm xử lý cho tín hiệu

Sử dụng lệnh trap, chúng ta gán hàm xử lý cho các tín hiệu:

bash Copy
trap send_email_notification 'SIGRTMIN+1'
trap enable_debug_logs 'SIGRTMIN+2'
trap disable_debug_logs 'SIGRTMIN+3'

Thực hiện tín hiệu

Bây giờ chúng ta sẽ chạy script bash trong một terminal và mở một terminal mới để gửi các tín hiệu thực thời. Sử dụng lệnh ps, lưu lại PID của script bash (trong trường hợp của tôi là 692560). Để gửi tín hiệu đến chương trình của chúng ta, chúng ta sẽ sử dụng lệnh bash kill. Cú pháp như sau:

Copy
kill -s <tên tín hiệu> <pid>

Thí nghiệm 1: yêu cầu gửi thông báo qua email

Chạy lệnh sau trong terminal thứ hai:

Copy
kill -s SIGRTMIN+1 692560

Bạn sẽ thấy thông báo "Đang gửi thông báo qua email" trong terminal đầu tiên sau vài giây.

Thí nghiệm 2: bật in ra nhật ký debug

Chạy lệnh sau:

Copy
kill -s SIGRTMIN+2 692560

Xem kết quả trong terminal đầu tiên. Bạn sẽ thấy chương trình bắt đầu in ra các dòng mà nó đang thực hiện.

Thí nghiệm 3: tắt in ra nhật ký debug

Chạy lệnh sau:

Copy
kill -s SIGRTMIN+3 692560

Chương trình sẽ dừng việc in ra các dòng thực hiện và trở lại trạng thái bình thường.

Kết luận

Chúng ta đã thấy cách dễ dàng để triển khai xử lý tín hiệu thực thời trong chương trình và thông báo cho tiến trình đang chạy bằng cách sử dụng tín hiệu gốc của Linux. Đây là cách đơn giản nhất để thông báo cho một tiến trình đang chạy mà không cần thiết lập socket, pipe hay dbus phức tạp. Mặc dù điều này không cho phép bạn gửi dữ liệu nếu tiến trình cần, nhưng nó rất tốt cho các tình huống mà tiến trình chỉ cần biết về một sự kiện để thực hiện công việc.

Bonus

Dưới đây là một chương trình python đơn giản triển khai ý tưởng trên. Hãy thử chạy nó và gửi các tín hiệu thực thời như trên để xem kết quả.

python Copy
#!/usr/bin/env python

import signal
import logging
import sys
import time

# Thiết lập logger
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG)
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
stdout_handler.setFormatter(formatter)
logger.addHandler(stdout_handler)

def send_email_notification(signum, frame):
    logger.info("Đang gửi thông báo qua email")

def enable_debug_logs(signum, frame):
    stdout_handler.setLevel(logging.DEBUG)
    logger.info("Bật ghi nhật ký debug")

def disable_debug_log(signum, frame):
    stdout_handler.setLevel(logging.INFO)
    logger.info("Tắt ghi nhật ký debug")

if __name__ == '__main__':
    signal.signal(signal.SIGRTMIN+1, send_email_notification)
    signal.signal(signal.SIGRTMIN+2, enable_debug_logs)
    signal.signal(signal.SIGRTMIN+3, disable_debug_log)

    while True:
        time.sleep(5)
        logger.info("Đang thực hiện công việc")
        logger.debug("đây là nhật ký debug cho công việc đó")

Tài liệu tham khảo

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào