shell daemon init.d/functions 守護進程 lock文件

http://shahmirj.com/blog/the-initd-script

http://nixcraft.com/showthread.php/13985-script-as-daemon

http://blog.n01se.net/blog-n01se-net-p-145.html

 

This is part 2 of my first tutorial on how to create a daemon script. I have been meaning to do this for a long time, so here it is. When I was starting out in Linux and trying to understand the whole process about daemons and how they work I was really overwhelmed and the main mistake I made was, that I thought the init.d script was some humounges language that I had to learn. This is not the case. Infact an init.d script is nothing but bash program which controls the following:

  • starting a process if its not started,

  • stopping it if it is started

  • stopping then starting the script, in other words restarting

Please be aware, that if you have never programmed using bash, It would be a good idea to brush up some common knowledge before carrying any further. Google should be more than sufficient.

Before starting I think its only right to begin with a full init.d script, To show the different parts which can be explained later in details. So without further ado, I introduce our Script:

#!/bin/bash
#
# Daemon Name: myprogd
#  
# chkconfig: - 58 74
# description: Our Script# Source function library.  裏面會包含daemonize的函數daemon
. /etc/init.d/functions    #在當前shell環境中source functions# Source networking configuration.
. /etc/sysconfig/myconfig

prog=myprogd
lockfile=/var/lock/subsys/$prog

start() { 
    #Make some checks for requirements before continuing
    [ "$NETWORKING" = "no" ] && exit 1
    [ -x /usr/sbin/$prog ] || exit 5

    # Start our daemon daemon
    echo -n $"Starting $prog: "
    daemon --pidfile /var/run/${proc}.pid $prog
    RETVAL=$?
    echo

    #If all is well touch the lock file
    [ $RETVAL -eq 0 ] && touch $lockfile
    return $RETVAL
}

stop() {
    echo -n $"Shutting down $prog: "
    killproc $prog
    RETVAL=$?
    echo

    #If all is well remove the lockfile
    [ $RETVAL -eq 0 ] && rm -f $lockfile
    return $RETVAL
}

# See how we were called.
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  status)
        status $prog
        ;;
  restart)
        stop
        start
        ;;
   *)
        echo $"Usage: $0 {start|stop|status|restart}"
        exit 2
esac

Scarry hunh? Well lets break it down. There are 6 main parts that this init script controls:

  • The shell deceleration !#/bin/bash

  • BEGIN INIT INFO - This is NOT a comment, this is actual deceleration. Took me a long while to recognize this

  • Include functions - . /etc/init.d/functions and . /etc/sysconfig/myconfig (Optional)

  • Variable deceleration - proc and lockfile

  • Local Function - start() and stop

  • case to handle actions

The shell Deceleration

#!/bin/bash
#
# Daemon Name: myprogd
#  
# chkconfig: - 58
# description: A script that does nothing but run a sleep process for testing
# requires: mysqld

This is the header that tells the shell what to use when running this script. The comment here are just for visual purposes but very important. Especially if you are planning on open-sourcing your script. You can choose to put more information here and be more descriptive, but i believe the headings above (Deamon Name, chkconfig, description and requires) are the most important ones, and should always be filled in.

The init inbuilt functions (in file /etc/init.d/functions)

init.d comes with a certian set of functions that help you control a daemon without going through the headache creating a processflow to control the starting and stopping of scripts. This file(/etc/init.d/functions) includes three major functions that we will be interested in daemonkillproc and status

daemon()

This function starts your process and creates the PID file. The PID file stores the process-id value given to a each and every process by your kernel. You can view the current running processes and their PIDs by running ps -ef.

The daemon function can take an optional parameter --pidfile, This is the location and the name of the PID file that the daemon will create. If the parameter is omitted by default it will create a pidfile in /var/run/$1.pid where $1is the program name. The variable lets you define the PID-file name yourself should you choose to

A PID file is a file that contains simply the process id, If you look inside the /var/run/ folder on your linux machine you will see many files that store a number inside them. This is used by the init script to kill that specific Process with that id. When you start your process up, the script will create a PID file with its process ID.

For our script we are going to define a custom PID file so we can see how its used in other script

killproc()

To kill any process on Linux you use the kill command, which takes a Process ID as a parameter. This function performs a similar task, however it uses the PID file to retrieve the Process ID, this is why you can stop a daemon without giving it a PID file to kill. You can see how the PID file is now becoming more and more important.

Once we perform our kill we also need to remove the PID file as the Process ID is now no longer in use. This functions also deals with the cleanup of the PID file so a new one can be generated next time around. From a PID file prespective, daemon() function creates it and the killproc() function deletes it.

status()

The status function just looks at the PID file and checks its running status.

I would like to point out all of the above function leave out other major functionality, this is done so someone starting out can understand the basics. I do recommend you look at the functions manually and work your way through them to understand all the different options.

To include these function we use

# Source function library.
. /etc/init.d/functions

Our start() function

start() { 
    #Make some checks for requirements before continuing
    [ "$NETWORKING" = "no" ] && exit 1
    [ -x /usr/sbin/$prog ] || exit 5

    # Start our daemon daemon
    echo -n $"Starting $prog: "
    daemon --pidfile /var/run/${proc}.pid $prog
    RETVAL=$?
    echo

    #If all is well touch the lock file
    [ $RETVAL -eq 0 ] && touch $lockfile
    return $RETVAL
}

This functions starts of with checking weather the variable $NETWORK is set to "no", if this is the case we will exit out of our starting procedures as we would like the networking to be on, before we go any where with our script. We don't actually require networking for our script, but I have left that in there just to show you how you can control dependencies for other environment in your system.

Where does the $NETWORK variable come from you ask? Well this is included and defined using the include just after the functions call.

# Source networking configuration.
. /etc/sysconfig/myconfig

If you look inside the file /etc/sysconfig/myconfig, you can see the following

NETWORKING_IPV6=no
HOSTNAME=local.shahmirj.com
NETWORKING=yes
GATEWAY=192.168.1.1

The next line [ -x /usr/sbin/$prog ] || exit 5 checks to see weather or not the actual daemon file exist for us to run or not. There is no point continuing further if the script we are trying to daemon-ize does not exist. You may need to change this according to where your script is located.

Continuing on, we see the echo to show some output of the script starting followed by a call to daemon --pidfile /var/run/${proc}.pid $prog. This call is just passing the actual starting of the script to our daemon function as explained above. Note our pidfile parameter call here is useless, because if left out it will create the exact same PID file as the parameter. I have left the pidfile parameter just for demonstration purposes.

After this call we use a $RETVAL variable to store the return code of the $prog. Which is relevant to what you set your daemon to return back to the shell inside your main function. Note that 0 is always the return code for success, this can be confusing but just try to remember this. This is also the reason why in many c++ examples you see main() returning 0 at the end.

This is also another reason why we fork() our script, because our child keeps on living where our parent process returns the exit code.

If we success in running your script, we touch a $lockfile. A lockfile is similar to a PID file however it serves a different purpose. A Lockfile in our context ensures only one script is running at one time. This prevents someone trying to start the script up twice. This is your responsibility as there may be certain circumstances where you want to keep starting new scripts. So after we succeed in running your script, we touch a lockfile. On a plus side this lock file can also tell us when your process started, by looking at the date the file was modified.

Our stop() Function

stop() {
    echo -n $"Shutting down $prog: "
    killproc $prog
    RETVAL=$?
    echo

    #If all is well remove the lockfile
    [ $RETVAL -eq 0 ] && rm -f $lockfile
    return $RETVAL
}

This is more or less the negative of the start function. Instead of starting the process it kills the existing process using a call to killproc and if the killing of the process succeds it removes the lock file.

The case call

# See how we were called.
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  status)
        status $prog
        ;;
  restart)
        stop
        start
        ;;
   *)
        echo $"Usage: $0 {start|stop|status|restart}"
        exit 2
esac

This call just handles which function to run for the right call. For example

> /etc/init.d/myprogd start
> /etc/init.d/myprogd stop
> /etc/init.d/myprogd restart
> /etc/init.d/myprogd status

Thats all there is to it folks, Leave a comment if you want me to elaborate more on a specific part that you may be lost at. Otherwise go forth and daemon-ize.

 

=======================================================

A lock file is used with linux daemons to prevent the script from running multiple instances of itself. 

Let me Explain the following code block........

Code:

if [ ! -e $lockfile ]; then
   touch $lockfile
   critical-section
   rm $lockfile
else
   echo "critical-section is already running"
fi

The if statement uses the -e (man test) file test operator to check if the variable $lockfile exists. If the lockfile is in place the script will NOT!!! run again. Notice the rm $ lockfile at the end of the code. Once your critical-section or script finishes, it will clean up after itself by removing the lock file. 


Lets have a look at NRPE daemon. Nagios uses this daemon to run commands on remote servers. You will see the locking mechanism getting called. Most daemons will use the path /var/lock/subsys to write and delete lockfiles.Focus on the red sections. 

Code:

NrpeBin=/usr/local/nagios/nrpe
NrpeCfg=/etc/nagios/nrpe.cfg  ## Define Lock file

# See how we were called.
case "$1" in
  start)
        # Start daemons.
        echo -n "Starting nrpe: "         $NrpeBin -c $NrpeCfg -d 
        echo          ## create the file when started
        ;;
  stop)
        # Stop daemons.
        echo -n "Shutting down nrpe: "         nrpe    
        echo ## remove the file when stopped.
        ;;
  restart)
        $0 stop
        $0 start
        ;;
  status)
        status nrpe
        ;;
  *)
        echo "Usage: nrpe {start|stop|restart|status}"
        exit 1
esac

exit 0

=====================================================================
如何能不讓腳本重複執行的問題,實際就是文件鎖的概念

在SHELL中實現文件鎖,有兩種簡單的方式。

一是利用普通文件,在腳本啓動時檢查特定文件是否存在,如果存在,則等待一段時間後繼續檢查,直到文件不存時創建該文件,在腳本結束時刪除文件。爲確保腳本在異常退出時文件仍然能被刪除,可以藉助於trap "cmd" EXIT TERM INT命令。一般這類文件存放在/var/lock/目錄下,操作系統在啓動時會對該目錄做清理。

另一種方法是是使用flock命令。使用方式如下,這個命令的好處是等待動作在flock命令中完成,無需另外添加代碼。

(  flock 300  ...cmd...  flock -u 300  ) > /tmp/file.lock

但flock有個缺陷是,在打開flock之後fork(),子進程也會擁有鎖,如果在flock其間有運行daemon的話,必需確保daemon在啓動時已經關閉了所有的文件句柄,不然該文件會因爲daemon一直將其置於打開狀態而無法解鎖。

 

/var/lock/subsys目錄的作用的說明

很多程序需要判斷是否當前已經有一個實例在運行,這個目錄就是讓程序判斷是否有實例運行的標誌,比如說xinetd,如果存在這個文件,表示已經有xinetd在運行了,否則就是沒有,當然程序裏面還要有相應的判斷措施來真正確定是否有實例在運行。

通常與該目錄配套的還有/var/run目錄,用來存放對應實例的PID,如果你寫腳本的話,會發現這2個目錄結合起來可以很方便的判斷出許多服務是否在運行,運行的相關信息等等。

 

   實際上,判斷是否上鎖就是判斷這個文件,所以文件存在與否也就隱含了是否上鎖。 而這個目錄的內容並不能表示一定上鎖了,因爲很多服務在啓動腳本里用touch來創建這個加鎖文件,在系統結束時該腳本負責清除鎖,這本身就不可靠(比如 意外失敗導致鎖文件仍然存在),我在腳本里一般是結合PID文件(如果有PID文件的話),從PID文件裏得到該實例的PID,然後用ps測試是否存在該 PID,從而判斷是否真正有這個實例在運行,更加穩妥的方法是用進程通訊了,不過這樣的話單單靠腳本就做不到了。

================================

 

To run it as a full daemon from a shell, you'll need to use setsid and redirect its output. You can redirect the output to a logfile, or to /dev/null to discard it. Assuming your script is called myscript.sh, use the following command:

setsid myscript.sh >/dev/null 2>&1 < /dev/null &

This will completely detach the process from your current shell (stdin, stdout and stderr). If you want to keep the output in a logfile, replace the first /dev/null with your /path/to/logfile.

You have to redirect the output, otherwise it will not run as a true daemon (it will depend on your shell to read and write output).

 

 

Also look into creating a lock file mechanism to check that the script is not running on top of itself. 

Here is basic example of what I use when implementing lock files for daemons. 

Code:

if [ ! -e $lockfile ]; then
   touch $lockfile
   critical-section
   rm $lockfile
else
   echo "critical-section is already running"
fi

Or a more robust lockfile check.

Code:

if [ ! -e $lockfile ]; then
   touch $lockfile
   critical-section
   rm $lockfileelse
   echo "critical-section is already running"
fi


======================================================================
http://alsww.blog.51cto.com/2001924/1113112

1. fork  ( /directory/script.sh) :如果shell中包含執行命令,那麼子命令並不影響父級的命令,在子命令執行完後再執行父級命令。子級的環境變量不會影響到父級。 

fork是最普通的, 就是直接在腳本里面用/directory/script.sh來調用script.sh這個腳本.

運行的時候開一個sub-shell執行調用的腳本,sub-shell執行的時候, parent-shell還在。

sub-shell執行完畢後返回parent-shell. sub-shell從parent-shell繼承環境變量.但是sub-shell中的環境變量不會帶回parent-shell

2. exec (exec /directory/script.sh):執行子級的命令後,不再執行父級命令。

exec與fork不同,不需要新開一個sub-shell來執行被調用的腳本.  被調用的腳本與父腳本在同一個shell內執行。但是使用exec調用一個新腳本以後, 父腳本中exec行之後的內容就不會再執行了。這是exec和source的區別

3. source (source /directory/script.sh):執行子級命令後繼續執行父級命令,同時子級設置的環境變量會影響到父級的環境變量。

與fork的區別是不新開一個sub-shell來執行被調用的腳本,而是在同一個shell中執行. 所以被調用的腳本中聲明的變量和環境變量, 都可以在主腳本中得到和使用.


轉自:

https://www.cnblogs.com/my_life/articles/4323525.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章