Cách tạo Daemon trên Linux

Daemon là các tiến trình không chạy trực tiếp dưới sự kiểm soát của người dùng mà chạy ở chế độ nền. Thông thường, chúng bắt đầu khi khởi động hệ thống và chạy liên tục cho đến khi hệ thống tắt. Sự khác biệt duy nhất giữa các tiến trình này và tiến trình bình thường là chúng không gửi thông báo đến console hoặc màn hình theo bất kỳ cách nào.

Đây là cách bạn có thể tạo daemon trên máy Linux.

Giới thiệu ngắn gọn về cách daemon được tạo ra

Rất nhiều daemon chạy trên hệ thống và một số ví dụ về daemon quen thuộc như sau:

  • crond: Làm cho các lệnh chạy vào thời gian được chỉ định
  • sshd: Cho phép đăng nhập vào hệ thống từ các máy từ xa
  • httpd: Cung cấp các trang web
  • nfsd: Cho phép chia sẻ file qua mạng

Ngoài ra, các tiến trình daemon thường được đặt tên kết thúc bằng chữ d, mặc dù điều này là không bắt buộc.

Để tiến trình chạy dưới dạng daemon, hãy làm theo quy trình sau:

  • Các hoạt động ban đầu, chẳng hạn như đọc file cấu hình hoặc lấy tài nguyên hệ thống cần thiết, phải được thực hiện trước khi tiến trình trở thành một daemon. Bằng cách này, hệ thống có thể báo cáo các lỗi đã nhận cho người dùng và quá trình sẽ được kết thúc bằng một mã lỗi phù hợp.
  • Một tiến trình chạy nền được tạo với init là tiến trình mẹ của nó. Với mục đích này, một tiến trình con được tách từ tiến trình init đầu tiên, và sau đó tiến trình trên được kết thúc.
  • Một phiên mới sẽ mở bằng cách gọi hàm setid và tiến trình này sẽ được ngắt kết nối khỏi terminal.
  • Tất cả các trình mô tả file đang mở được kế thừa từ tiến trình mẹ đều bị đóng.
  • Đầu vào, đầu ra và thông báo lỗi tiêu chuẩn được chuyển hướng đến /dev/null.
  • Thư mục làm việc của tiến trình phải thay đổi.

Phiên Daemon là gì?

Sau khi đăng nhập vào hệ thống thông qua terminal, người dùng có thể chạy nhiều ứng dụng thông qua chương trình shell. Các tiến trình này sẽ đóng khi người dùng thoát khỏi hệ thống. Hệ điều hành nhóm các tiến trình này thành những nhóm phiên và tiến trình.

Mỗi phiên bao gồm các nhóm tiến trình. Có thể mô tả tình huống này như sau:

Phiên Daemon
Phiên Daemon

Terminal nơi các tiến trình nhận đầu vào và gửi đầu ra của chúng được gọi là controlling terminal. Mỗi controlling terminal chỉ được liên kết với một phiên tại một thời điểm.

Một phiên và các nhóm tiến trình trong đó có số nhận dạng (ID). Các số nhận dạng này là số nhận dạng tiến trình (PID) của group leader phiên và tiến trình. Một tiến trình con chia sẻ cùng một nhóm với tiến trình mẹ của nó. Khi nhiều quy trình đang giao tiếp với cơ chế pipe, tiến trình đầu tiên sẽ trở thành process group leader.

Tạo tiến trình Daemon trên Linux

Ở đây, bạn sẽ thấy cách bạn có thể tạo một hàm daemon. Với mục đích này, bạn sẽ tạo một hàm có tên _daemon. Bạn có thể bắt đầu bằng cách đặt tên mã ứng dụng sẽ chạy dưới dạng daemon là test.c và mã bạn sẽ tạo hàm daemon là daemon.c.

//test.c
#include <stdio.h>
int _daemon(int, int);
int main()
{
	getchar();
	_daemon(0, 0);
	getchar();
	return 0;
}
//daemon.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/fs.h>
#include <linux/limits.h>
int _daemon(int nochdir, int noclose) {
 pid_t pid;
 pid = fork(); // Fork off the parent process
 if (pid < 0) {
   exit(EXIT_FAILURE);
 }
 if (pid > 0) {
   exit(EXIT_SUCCESS);
 }
 return 0;
}

Để tạo một daemon, bạn cần một tiến trình nền có tiến trình mẹ là init. Trong đoạn code trên, _daemon tạo một tiến trình con và sau đó kết thúc tiến trình mẹ. Trong trường hợp này, tiến trình mới của bạn sẽ là một tiến trình con của init và sẽ tiếp tục chạy ở chế độ nền.

Bây giờ, hãy biên dịch ứng dụng bằng lệnh sau và kiểm tra trạng thái của tiến trình trước và sau khi _deamon được gọi:

gcc -o test test.c daemon.c

Chạy ứng dụng và chuyển sang một terminal khác mà không cần nhấn bất kỳ phím nào khác:

./test

Bạn có thể thấy rằng các giá trị liên quan đến tiến trình của bạn như sau. Tại đây, bạn sẽ phải sử dụng lệnh ps để nhận thông tin liên quan đến tiến trình. Trong trường hợp này, hàm _daemon vẫn chưa được gọi.

ps -C test -o "pid ppid pgid sid tty stat command"
# Output
PID    PPID    PGID     SID TT       STAT COMMAND
10296  5119   10296    5117 pts/2    S+   ./test 

Khi nhìn vào trường STAT, bạn thấy rằng tiến trình của bạn đang chạy nhưng đang đợi một sự kiện không đúng lịch trình xảy ra sẽ khiến nó chạy ở foreground.

Viết tắtÝ nghĩa
SĐang ngủ chờ một sự kiện xảy ra
TỨng dụng đã dừng
sSession leader
+Ứng dụng đang chạy ở foreground

Bạn có thể thấy rằng tiến trình chính của ứng dụng là shell như mong đợi.

ps -jp 5119                                 
# Output      
PID    PGID     SID TTY          TIME CMD
5119   5119    5117 pts/2    00:00:02 zsh

Bây giờ quay lại terminal nơi bạn đang chạy ứng dụng của mình và nhấn Enter để gọi hàm _daemon. Sau đó, xem lại thông tin tiến trình trên terminal khác.

ps -C test -o "pid ppid pgid sid tty stat command"
# Output
PID    PPID    PGID     SID TT       STAT COMMAND
22504     1   22481    5117 pts/2    S    ./test

Trước hết, bạn có thể nói rằng tiến trình con mới đang chạy ở chế độ nền vì bạn không thấy ký tự + trong trường STAT. Bây giờ, hãy kiểm tra xem đâu là tiến trình mẹ của tiến trình bằng cách sử dụng lệnh sau:

ps -jp 1                                      
​​​​​​​# Output    
PID    PGID     SID TTY          TIME CMD
1      1        1   ?        00:00:01 systemd

Bây giờ, bạn có thể thấy rằng tiến trình mẹ cho tiến trình của bạn là systemd. Ở trên đã đề cập rằng đối với bước tiếp theo, một phiên mới sẽ mở ra và tiến trình này nên được ngắt kết nối khỏi controlling terminal. Đối với điều này, bạn sử dụng hàm setid. Thêm lệnh gọi này vào hàm _daemon của bạn.

Đoạn code cần thêm như sau:

if (setsid() == -1) 
	return -1;

Bây giờ bạn đã kiểm tra trạng thái trước khi _daemon được gọi, bây giờ bạn có thể xóa hàm getchar đầu tiên trong code test.c.

//test.c
#include <stdio.h>
int _daemon(int, int);
int main()
{
	_daemon(0, 0);
	getchar();
	return 0;
}

Sau khi biên dịch và chạy lại ứng dụng, hãy chuyển đến terminal nơi bạn đã thực hiện các đánh giá của mình. Trạng thái mới của tiến trình như sau:

ps -C test -o "pid ppid pgid sid tty stat command"
​​​​​​​# Output
PID    PPID    PGID     SID TT       STAT COMMAND
25494     1   25494   25494 ?        Ss   ./test

Các ký hiệu ? trong trường TT cho biết rằng tiến trình của bạn không còn được kết nối với terminal. Lưu ý rằng các giá trị PID, PGID và SID trong tiến trình của bạn giống nhau. Tiến trình của bạn bây giờ là một session leader.

Trong bước tiếp theo, thay đổi thư mục làm việc thành thư mục gốc theo giá trị của đối số bạn đã truyền. Bạn có thể thêm đoạn code sau vào hàm _daemon cho việc này:

if (!nochdir) {
   if (chdir("/") == -1)
      return -1;
}

Bây giờ, theo đối số được truyền, có thể đóng tất cả các trình mô tả file. Thêm code sau vào hàm _daemon:

#define NR_OPEN 1024
if (!noclose) {
   for (i = 0; i < NR_OPEN; i++)
      close(i);
   open("/dev/null", O_RDWR);
   dup(0);
   dup(0);
}

Sau khi tất cả các trình mô tả file được đóng, những file mới được mở bởi daemon sẽ được hiển thị với các trình mô tả 0, 12 tương ứng. Trong trường hợp này, ví dụ, các lệnh printf trong code sẽ được chuyển hướng đến file thứ hai được mở. Để tránh điều này, 3 số nhận dạng đầu tiên trỏ đến thiết bị /dev/null.

Trong trường hợp này, trạng thái cuối cùng của hàm _daemon sẽ như sau:

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
int _daemon(void) {
 // PID: Process ID
 // SID: Session ID
 pid_t pid, sid;
 pid = fork(); // Fork off the parent process
 if (pid < 0) {
   exit(EXIT_FAILURE);
 }
 if (pid > 0) {
   exit(EXIT_SUCCESS);
 }
 // Create a SID for child
 sid = setsid();
 if (sid < 0) {
   // FAIL
   exit(EXIT_FAILURE);
 }
 if ((chdir("/")) < 0) {
   // FAIL
   exit(EXIT_FAILURE);
 }
 close(STDIN_FILENO);
 close(STDOUT_FILENO);
 close(STDERR_FILENO);
 while (1) {
   // Some Tasks
   sleep(30);
 }
 exit(EXIT_SUCCESS);
}

Đây là một ví dụ về đoạn code chạy ứng dụng sshd dưới dạng daemon:

...
if (!(debug_flag || inetd_flag || no_daemon_flag)) {
    int fd;
    if (daemon(0, 0) < 0)
        fatal("daemon() failed: %.200s", strerror(errno));
    /* Disconnect from the controlling tty. */
    fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
    if (fd >= 0) {
        (void) ioctl(fd, TIOCNOTTY, NULL);
        close(fd);
    }
}
...

Daemon là các chương trình thực hiện những hành động khác nhau theo cách thức xác định trước được thiết lập để đáp ứng các sự kiện nhất định. Chúng chạy âm thầm trên máy Linux của bạn. Chúng không chịu sự kiểm soát trực tiếp của người dùng và mỗi service chạy nền đều có daemon của nó.

Điều quan trọng là phải nắm vững các daemon để tìm hiểu cấu trúc kernel của hệ điều hành Linux và hiểu hoạt động của những kiến trúc hệ thống khác nhau.

Thứ Sáu, 20/05/2022 08:16
52 👨 1.085
0 Bình luận
Sắp xếp theo