您的位置:首页 > 运维架构 > Linux

用lazarus开发Linux daemon程序及编写启动脚步

2014-10-20 10:31 465 查看
用lazarus开发Linuxdaemon程序及编写启动脚步
 

Linux Daemon程序简介

Linux Daemon程序(守护进程或精灵进程)是生存期较长的一种进程。它们常常在系统自举时启动,仅当系统关闭时才终止。所以它们没有控制终端,在后台运行。

在Linux C环境下编写daemon程序,需要遵循一些基本规则,以便防止产生并不需要的交互作用。比如其中有两条重要的规则。

第一,要调用fork,然后父进程退出。这样做有两个目的:

(1)如果该daemon进程是作为一条shell命令启动的,那么父进程终止使得shell认为这条命令已经执行完毕;

(2)子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了子进程不是一个进程组的组长进程。这对于之后要调用的setsid是必要的前提条件。

第二,调用setsid会创建一个新会话,调用进程成为新会话的首进程,并成为一个新进程组的组长,最重要的该调用导致进程没有了控制终端。

但使用lazarus开发daemon服务程序,不用这么麻烦,只需要建立daemon工程,在其上编写代码即可。

Lazarus下开发daemon程序

安装lazarus

根据自己的系统类型,从 https://sourceforge.net/projects/lazarus/files/下载合适的rpm安装包到本地目录,包括如下三个文件:

fpc-2.6.2-0.laz.i686.rpm

fpc-src-2.6.2-0.laz.i686.rpm

lazarus-1.0.8-0.i686.rpm

命令行,进入下载目录,安装:

Rpm -ivh *.rpm

Lazarus在Linux系统默认安装在/usr/lib/lazarus目录中。

安装lazdaemon开发包

在lazarus ide的package->openpackage file(.lpk)菜单打开/usr/lib/lazarus/components/daemon/lazdaemon.lpk工程,如图:



图1 安装lazdaemon
依次compile,use->install。安装lazdaemon包,会重新编译lazaruside,并自动重启ide。

在lazarus ide的file菜单中,点击new…,弹出界面如下图,如果出现Daemon(service)applications项目,说明安装成功。



图2 新建daemonservice应用程序

Daemon例子

该例子以定期向一个注册服务器发送注册信息为例,注册服务器的代码这里没有给出,该例子主要是为了说明daemon程序如何编写。

在图2的截图中,新建Daemon(service) application项目。新建后,界面如下图。



新建工程后,默认有两个单元文件,分别是daemonMapperunit1.pas和daemonunit1.pas文件,当然还有其相应的窗体文件。

编写daemonunit1.pas单元文件内容如下:

unitDaemonUnit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes,SysUtils,FileUtil,DaemonApp,sockets,eventlog;
 
type
 
  { TRegThread }
  TRegThread=class(TThread)
  private
    FLog:TEventLog;
    procedureDoLog(Msg:String);
  public
    ConstructorCreate(ALog:TEventLog);
    ProcedureExecute;override;
  end;
 
  { TDaemon1 }
  TDaemon1=class(TDaemon)
    procedureDataModuleStart(Sender:TCustomDaemon;varOK:Boolean);
  private
    { private declarations }
    FLog:TEventLog;
    FThread:TRegThread;
    procedureStartLog;
  public
    { public declarations }
  end;
 
var
  Daemon1:TDaemon1;
 
implementation
 
procedureRegisterDaemon;
begin
  RegisterDaemonClass(TDaemon1)
end;
 
{$R *.lfm}
 
{ TRegThread }
procedureTRegThread.DoLog(Msg:String);
begin
  IfAssigned(FLog)then
    FLog.Info(Msg);
end;
 
constructorTRegThread.Create(ALog:TEventLog);
begin
  FLog:=Alog;
  InheritedCreate(False);
end;
 
procedureTRegThread.Execute;
var
  buf:string;
  SAddr:TInetSockAddr;
  s:Longint;
  sin,sout:text;
  inaddr:in_addr;
  i:integer;
const
  CRLF= #13#10;
begin
  whiletruedo
  begin
  dolog('heart frame start...');
  S:=fpSocket(AF_INET,SOCK_STREAM,0);
  ifs=-1then
  begin
    dolog('fpsocket error:'+inttostr(socketerror));
    exit;
  end;
  SAddr.sin_family:=AF_INET;
  SAddr.sin_port:=htons(10001);
  SAddr.sin_addr.s_addr:=hosttonet((10shl24)or(11shl16)or(32shl8)or(59));
  ifnotconnect(s,SAddr,Sin,Sout)then
  begin
    dolog('connect error:'+inttostr(socketerror));
    continue;
  end;
  reset(sin);
  rewrite(sout);
    begin
      buf:=Char($FF);
      buf:=buf+ 'cxqTest'+CRLF+
'10000'+CRLF;
      buf:=buf+ 'svn';
      buf:=buf+CRLF+ '1.0';
      writeln(sout,buf);
    end;
    dolog(buf);
    flush(sout);
    close(sout);
    closesocket(s);
    sleep(10000);
  end;
end;
 
{ TDaemon1 }
procedureTDaemon1.DataModuleStart(Sender:TCustomDaemon;varOK:Boolean);
begin
  sleep(10000);
  startlog;
  FThread:=TRegThread.Create(flog);
end;
 
procedureTDaemon1.StartLog;
begin
  FLog:=Self.Logger;
  //FLog.FileName:= 'cxqcxq.txt';
  //flog.LogType:=ltfile;
end;
 
initialization
  RegisterDaemon;
end.
在这个例子中,DaemonMapperunit1.pas单元不用写任何代码,但需要设置其窗体属性。点击daemonMapper1窗体,在左边的objectinspector窗口中显示该窗体的属性列表。



点击daemonDefs属性后面的省略号,弹出一个编辑窗口,新建一个条目,并设置属性如下,其中DaemonClassName属性要和daemonunit1.pas单元中的继承于TDaemon类的类名相同。



设置好,就可以编译该程序了。但是,在linux下编译多线程程序时,需要在工程单元文件中,注释掉一个条件宏,否则该程序不能正常运行。这应该是lazarus ide的一个bug。代码如下。

Programproject1;
 
Uses
{$IFDEF UNIX}//{$IFDEF UseCThreads}
  CThreads,
{$ENDIF}//{$ENDIF}
  DaemonApp,lazdaemonapp,DaemonMapperUnit1,DaemonUnit1
  { add your units here };
 
begin
  Application.Initialize;
  Application.Run;
end.   
这样,就可以编译并测试了。

手工运行daemon测试

在命令行下进入源程序目录,假设编译的程序名称是project1,执行project1 –r。如果想在后台执行,执行 project1 –r &。

要想在Linux开机时自动启动该程序,还需要配置启动脚本。由于不同的系统有不同的配置方法,我们先看看系统版本再配置启动脚本,下面以两个真实系统为例。

查看Linux版本命令

可用如下命令:

lsb_release cat /proc/version  cat/etc/issue  cat /etc/redhat-release

在fedora系统中执行各命令如下。



在redhat6.3系统中执行各命令如下。



启动脚本

在redhat Linux系统中,根据启动级别,执行不同集合的启动脚本。各级别的启动脚本分别放在rcN.d目录中,init进程会根据用户配置的启动等级,启动相应rcN.d目录下的脚本。在rcN.d目录中的脚本都是符号链接,真正的脚本放在/etc/rc.d/init.d/目录中。符号连接名称体现出是启动还是停止,以及优先级。所以,如果要想在开机启动自己的程序,需要在/etc/rc.d/init.d/目录中放启动脚本,并在各rcN.d目录中放符号连接。比如Linux代理程序SVNServerService程序,该程序放在/home/ftp/incoming/linux目录中,编写对应的控制脚本如下:

#!/bin/bash
#
# SVNServerAgent        Startup script for the SVNServerAgent Server
#
# chkconfig: 2345 85 15
# description: The SVNServerAgent Server is an efficient and extensible  \
#              server implementing the SVNServer cmd Agent.
# processname: SVNServerService
#
### BEGIN INIT INFO
# Provides: SVNServerAgent
# Required-Start: $local_fs $remote_fs $network $named
# Required-Stop: $local_fs $remote_fs $network
# Should-Start: distcache
# Short-Description: start and stop SVNServerAgent Server
# Description: The Apache HTTP Server is an extensible server
#  implementing the SVNServer cmd Agent.
### END INIT INFO
 
SVC_START_OPTIONS="-r"
SVC_STOP_OPTIONS="s"
 
# Edit SVC_ALIAS to the long description of your service application
SVC_ALIAS="SVNServerAgent server"
# Edit SVC_FILENAME to the actual name of your compiled service application
SVC_FILENAME="SVNServerService"
 
# Edit SVC_DIR to where you place your compiled service application
SVC_DIR="/home/ftp/incoming/linux/"
 
# Edit SVC_SERVICE_SCRIPT to the name of this file without the extension
SVC_SERVICE_SCRIPT="SVNServerAgent"
# this will become your service name.  Ie.) service YourService start
 
SVC_FILE=$SVC_DIR$SVC_FILENAME
start(){
        if
[ -f
$SVC_FILE ]; then

          #reset      
          echo -n"Starting "$SVC_ALIAS": "
          RETVALS=$(start-stop-daemon -S -b -x $SVC_FILE -- $SVC_START_OPTIONS)
 
          Count=${#RETVALS[@]}
          RETVAL="[FAIL]"
 
          if
[ $Count
-
eq 0 ];then
            RETVAL="[OK]"
          elif
[ $Count
-
eq 1 ];then
            if
[
${#RETVALS[0]}-eq
0];
then

              RETVAL="[OK]"
            else
              iStart=${#SVC_FILE}
              iLength=${#RETVALS[0]}
              Response=${RETVALS[0]:(iStart+1):7}
              RETVAL=$Response
              if
[
"$Response" =="already"
];then
                RETVAL="[OK]"
              fi
            fi
          fi
          echo
$RETVAL

          return0
        else
          echo
$SVC_ALIAS
" not installed"
$SVC_DIR

          exit
2;
        fi
}
 
stop(){
        echo -n
"Shutting down "$SVC_ALIAS":"
        RETVALS=$(start-stop-daemon -K -x $SVC_FILE -- $SVC_STOP_OPTIONS)
        #additional PROCKILLS=$(killall -w -q -e $SVC_PROCESS_NAME $SVC_FILENAME)
        Count=${#RETVALS[@]}
        Index=0
        RETVAL="[FAIL]"
        if
[ $Count
-
eq 1 ];then
                if
[
${#RETVALS[0]}-eq
0];
then

                        RETVAL="[OK]"
                else
                        Response=${RETVALS[0]:0:2}
                        RETVAL=$Response
                        if[
"$Response"==
"No"];
then

                                RETVAL="[OK]"
                        fi
                fi
        else
                RETVAL="[OK]"
        fi
 
        echo
$RETVAL

        return
0
}
 
case
"$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status $SVC_SERVICE_SCRIPT
        ;;
    restart)
        stop
        start
        ;;
    *)
        echo
$SVC_ALIAS
" [Invalid Startup Parameters]"       
        echo
"Usage:  {start|stop|status|restart}"
        exit
1
        ;;
esac
exit
$?

把该脚本放在/etc/rc.d/init.d/目录中,用chkconfig命令工具在各rcN.d目录中生成对应的启动和停止脚步符号连接。注意,用chkconfig工具管理该脚本,需要在脚本的头部按照指定格式编写注释。其中代码的第6行,分表表示启动的级别,启动优先级,停止优先级。

Chkconfig –add命令可以增加管理的脚本。Chkconfig根据脚本第6行注释在各rcN.d目录中生成对应的符号连接文件,详细见下图命令结果。



从上图中可以看到,chkconfig命令在rc2,rc3,rc4,rc5目录中生成了启动脚本,以S(start)开头,之后是启动等级,最后是脚本名称;除此,还在其他的rcN.d目录中生成了以K开头的符号连接,这表示kill。手工也可以完成该任务,但用chkconfig工具更方便。

在fedora中,用systemctl控制脚本的执行启动,该脚本是个ini文件。

[Unit]

Description=SVNServer

After=network.target

 

[Service]

Type=simple

ExecStart=/home/ftp/incoming/linux/SVNServerService -r

RemainAfterExit=yes

TimeoutSec=25

 

[Install]

WantedBy=multi-user.target

把该脚本保存为SVNServerAgent.service文件,并放到/lib/systemd/system目录里。用systemctl enable SVNServerAgent.service命令激活该脚步在启动时执行。

服务启停和状态

使用service脚本命令或systemctl控制服务启停和查看状态。Service最终调用的是服务程序本身+参数方式。

Service serviceName start

Service serviceName stop

Service serviceName status

以及

Systemctl status serverName

Systemctl enable serverName(使脚本在系统启动时执行)

Systemctl add serverName

查看状态:

Ps –axj(查看所有无终端的进程)

lsof -Pln +M -i4

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息