Vietnamese Developers’ Blog

Viết daemon trên Linux (2)

Posted in Unix/Linux/BSD by kiennguyen on July 3, 2008

Nguồn: http://www.enderunix.org/docs/eng/daemon.php
Bài viết này bổ xung một số vấn đề chưa được trình bày trong bài viết “Viết daemon trên Linux (1)” của anh Hoàng.

1- Logging
Chúng ta có thể lựa chọn một trong hai cách ghi log như sau:
- Tự viết hàm ghi log:
Một hàm ghi log có dạng như sau:

void log_message( const char *fname, const char *msg )
{
  FILE *logfile = fopen( fname, “a” );

  if( ! logfile )
    return;

  fprintf( logfile, “%s\n”, msg );

  fclose( logfile );

}

- Dùng các hàm có sẵn trong thư viện: Standard C library có các hàm syslog(), openlog(), closelog() phục vụ việc ghi log.

2- Cơ chế loại trừ lẫn nhau
Tại một thời điểm thường chỉ có một thực thể của daemon đang chạy. Một thực thể của daemon sẽ cố gắng khóa một file (lock file). Nếu khóa thành công nghĩa là chưa có thực thể nào khác của daemon đó đang chạy. Khi đó, pid của thực thể sẽ được ghi vào lock file. Chúng ta sử dụng hàm chuẩn lockf() như sau:

lfp = open( "exampled.lock", O_RDWR | O_CREAT, 0640 );

if ( lfp < 0 ) /* không mở được lock file */
  exit( EXIT_FAILURE );

if ( lockf( lfp, F_TLOCK, 0 ) < 0 )
  /* không khóa được lock file, một thực thể khác đang chạy  */
  exit( EXIT_SUCCESS );

/* chưa có thực thể nào đang chạy, ghi pid vào lock file */
sprintf( str,"%d\n", getpid() );
write( lfp, str, strlen(str) );

3- Bắt các tín hiệu gửi đến
Sau các bước chuẩn bị nói trên, một daemon sẽ bắt đầu lắng nghe tín hiệu gửi đến từ người dùng hoặc từ các tiến trình khác. Chúng ta viết một hàm xử lí tín hiệu, sau đó gán hàm đó với các tín hiệu cụ thể nhờ hàm chuẩn signal():

void signal_handler( const int sig ) /* signal handler function */
{
  switch( sig ) {

    case SIGHUP:
      log_message( LOG_FILE, "hangup signal catched" );
      break;

    case SIGTERM:
      log_message( LOG_FILE, "terminate signal catched" );
      exit( EXIT_SUCCESS );

  }

}

signal( SIGHUP, signal_handler ); /* hangup signal */
signal( SIGTERM, signal_handler ); /* software termination signal from kill */

4- Chương trình hoàn chỉnh
Chương trình dưới đây là sự phát triển của chương trình trong bài viết trước

/*
Dịch chương trình: cc –o example example.c
Chạy chương trình: ./example
Test daemon: ps –ef | grep example
Test log: tail –f example.log
Test signal: kill –HUP `cat example.lock`
Terminate daemon: kill `cat example.lock`
*/

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include 

const char *LOG_FILE  = “example.log”;
const char *LOCK_FILE = “example.lock”;

void log_message( const char *fname, const char *msg )
{
  FILE *logfile = fopen( fname, “a” );

  if( ! logfile )
    return;

  fprintf( logfile, “%s\n”, msg );

  fclose( logfile );

}

void signal_handler( const int sig ) /* signal handler function */
{
  switch( sig ) {

    case SIGHUP:
      log_message( LOG_FILE, "hangup signal catched" );
      break;

    case SIGTERM:
      log_message( LOG_FILE, "terminate signal catched" );
      exit( EXIT_SUCCESS );

  }

}

int main(void) {

  /* Our process ID and Session ID */
  pid_t pid, sid;

  /* Fork off the parent process */
  pid = fork();
  if (pid  0) {
    exit( EXIT_SUCCESS );
  }

  /* Change the file mode mask */
  umask(0);

  /* Create a new SID for the child process */
  sid = setsid();
  if (sid < 0) {
    /* Log the failure */
    exit( EXIT_FAILURE );
  }

  /* Change the current working directory */
  if ( (chdir("/")) < 0 ) {
    /* Log the failure */
    exit(EXIT_FAILURE);
  }

  /* Close out the standard file descriptors */
  close(STDIN_FILENO);
  close(STDOUT_FILENO);
  close(STDERR_FILENO);

  /* Daemon-specific initialization goes here */
  lfp = open( LOCK_FILE, O_RDWR|O_CREAT, 0640 );

  if (lfp < 0) /* cannot open lock file*/
    exit( EXIT_FAILURE );

  if ( lockf(lfp,F_TLOCK,0) < 0 ) /* cannot lock */
    exit( EXIT_SUCCESS );

  /* first instance continues */
  sprintf( str, "%d\n", getpid() );
  write( lfp, str, strlen(str) ); /* record pid to lockfile */

  signal( SIGCHLD, SIG_IGN ); /* ignore child */
  signal( SIGTSTP, SIG_IGN ); /* ignore tty signals */
  signal( SIGTTOU, SIG_IGN );
  signal( SIGTTIN, SIG_IGN );
  signal( SIGHUP, signal_handler ); /* catch hangup signal */
  signal( SIGTERM, signal_handler); /* catch kill signal */

  /* The Big Loop */
  while (1) {
    /* Do some task here ... */
    sleep(30); /* wait 30 seconds */
  }

  exit(EXIT_SUCCESS);

}
Tagged with: ,

2 Responses

Subscribe to comments with RSS.

  1. Hoang Tran said, on July 4, 2008 at 9:05 am

    3. No, không phải chỉ vậy đâu.
    Hãy nghĩ rằng daemon chỉ giống như một process và mỗi một client connect đến daemon đó là một client process. Như vậy type of connection sẽ chính là cách thức để trao đổi giữa hai process. Người ta gọi đó là Inter-Processs Communication (IPC). Thông thường ở trên Linux có mấy cách trao đổi giữa các process là:
    - Signal Handling – Trong PMS mà chú đang làm thì toàn sử dụng cái này thôi
    - Message Queue (sys/msg.h)
    - Shared Memory hoặc qua pipe
    - Socket (Linux Socket, BSD Socket, Winsock, …): Cái này cho phép giao tiếp qua TCP/IP nên server (daemon) có thể chạy ở một máy chủ và các client process thì chạy ở phía các máy client chứ không nhất thiết trên cùng một máy.
    Như vậy thì để viết một cái skeleton cho daemon thì tốt nhất không bao gồm phần này mà sẽ phụ thuộc vào kiểu IPC mà người thiết kế muốn. Cách viết cũng không cố định cho mỗi kiểu.
    Về phương pháp logging thì nên có một blog khác. Log4j là một thư viện tuyệt vời của java đã được port qua .Net và cả cpp nữa. Nhưng với C và Linux lại có những cách log khác nữa. Mỗi cách có những ưu điểm và nhược điểm riêng.

  2. Kiên said, on July 16, 2008 at 10:56 am

    Đồng ý. Nếu viết cẩn thận về IPC thì sẽ mất 1 quyển sách dày.


Leave a Reply