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