Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3183861
  • 博文数量: 443
  • 博客积分: 11301
  • 博客等级: 上将
  • 技术积分: 5679
  • 用 户 组: 普通用户
  • 注册时间: 2004-10-08 12:30
个人简介

欢迎加入IT云增值在线QQ交流群:342584734

文章分类

全部博文(443)

文章存档

2022年(1)

2021年(1)

2015年(2)

2014年(1)

2013年(1)

2012年(4)

2011年(19)

2010年(32)

2009年(2)

2008年(4)

2007年(31)

2006年(301)

2005年(42)

2004年(2)

分类:

2006-04-24 17:35:43

作者:Hunter Li, Sun Microsystems, Inc.

简介

Solaris 10的SMF机制向系统管理员提供了统一的服务管理平台,利用它可方便地对所辖服务进行配置和管理,还可以在必要的时候自动重启服务。SMF的配置功能不但可让系统管理员在服务下线的状态下通过svccfg(1M)配置服务属性,还可以在服务联机的状态下结合使用svcadm refresh命令,动态配置和启用最新属性。另外,SMF还提供编程接口API(包含在libscf.so库中),利用它可以取代用户的配置文件以及相应的配置读取解析逻辑,享受专业级配置工具svccfg(1M)带来的便利。本文将结合一个例子讲述如何在应用程序中使用libscf.so所提供的部分API实现SMF服务refresh方法。

服务配置概述

对于大部分服务类应用而言,为了适应运行环境的多变性,一般都会在服务程序中编写配置读取模块,在服务启动时通过读取配置来决定如何提供服务。比如,一个典型的网络服务可能在服务启动时通过读取配置文件决定网络服务端口、服务日志路径等。配置的存在形式有多种,比如配置文件、数据库记录,甚至简单到使用操作系统环境变量等。无论配置采用何种形式存在,为了能获取配置值,服务程序需要自行实现相应的配置读取和解析逻辑。

除了在服务启动时需要读取配置外,有些行业领域要求服务应用可在服务运行过程中具有动态采用新配置的功能。这就要求开发人员编写更复杂的逻辑以保证服务进程在运行过程中可在适当的时机,重新读取配置并采用新配置,同时又必须保证新配置的采用过程不会导致服务进程不稳定甚至崩溃。动态读取配置的时机可有多种方式,较简单的方法比如,通过向服务进程发送某个特定信号,服务进程接到信号后重新读取配置,然后根据新配置设置服务环境并继续提供服务。

SMF框架对服务配置的支持

在SMF中,配置被称为属性(property),配置值称为属性值(property value)。利用svccfg(1M)工具可以设置服务的属性和属性值。服务配置缺省保存在SMF全局资源库/etc/svc/repository.db中。 SMF的svc.configd进程负责存取资源库。当其他SMF进程或工具需要配置信息时,可以通过连接svc.configd进程从资源库中获取配置,同样也通过svc.configd将配置存入资源库。用户服务程序也可以通过使用libscf.so提供的API与svc.configd进程相连接,从资源库中存取服务配置。


配置SMF服务最简单的方式是使用svccfg(1M)工具。svccfg(1M)工具是一个交互式的配置修改工具,它包含许多简单实用的命令(具体命令可以在进入svccfg后使用help查询)。比如常用命令有:

  • import命令可将以xml格式定义的用户服务加载为SMF服务并启动运行。
  • delete命令可以删除用户服务。
  • list和select命令可以查看和选取系统中现有的服务。
  • listprop、editprop、setprop、delprop命令可以查看和修改当前服务的配置属性。

另外,SMF还支持配置快照(snapshot)功能,通过它可以保留服务在某一时刻的属性配置,并在需要的时候可通过revert将当前配置恢复为某一时刻的快照配置。快照相关的命令包括listsnap,selectsnap和revert。

无论采用何种方式修改了当前服务的配置,这些修改只影响到资源库而非正在运行的服务进程。如果要求当前服务能够马上应用新配置,则用户在退出svccfg(1M)工具后需要使用svcadm refresh命令指示相关服务重新读取配置并采用新配置。

在SMF框架中,start和stop方法是服务必须提供的,这是由于SMF需要调用start和stop方法来启动和停止相应的服务。而refresh方法则不是必要的,前提是用户服务不需要动态刷新启用新配置属性。如果用户服务需要动态刷新配置属性,则用户服务在定义时,必须提供服务refresh方法,而相应服务程序也必须实现适时重新读取配置的逻辑。

使用libscf.so的API实现refresh方法

正如前文提到的,配置有多种存在形式,不同存在形式读取方法也不同。另外,实现SMF的refresh方法也有多种方式,比如通过信号(singal)通知服务进程读取最新配置就是一种简单实用的方法。本节下文将结合一个例子讲述如何利用libscf.so提供的API读取保存在SMF全局资源库中的配置,并通过信号方式通知服务进程重新读取配置,从而实现SMF所需的refresh方法。

服务程序

是一个简单的程序myapp.c,它运行后将成为后台守护进程存在于系统中,并每间隔5秒钟向日志文件插入一行记录以报告自己的存在。虽然它实际上不向外提供任何服务,但该程序模拟了一般服务程序的设计结构和运行模式。即,程序运行后以守护进程形式存在于系统,程序头部有服务配置读取逻辑read_config()和服务初始化逻辑app_init(),中部使用sleep(5)模拟等待服务请求逻辑,通过向日志插入记录模拟服务请求处理逻辑,然后返回至循环开始处sleep(5)继续等待下一个服务请求等。只要在此结构上修改和扩充相应的逻辑就可以将此程序修改为一个真正的服务程序。

表1. /export/home/smfdemo/myapp.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/*****************************************************************************/
/* myapp.c */
/*****************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

/* global exit status code */
#define RUN_OK 0
#define CONFIG_ERROR 1
#define FATAL_ERROR 2

/* global variable declaration */
char filename[1025];
scf_handle_t *handle = NULL;
scf_scope_t *sc = NULL;
scf_service_t *svc = NULL;
scf_propertygroup_t *pg = NULL;
scf_property_t *prop = NULL;
scf_value_t *value = NULL;

/* function declaration */
int read_config(void);
int app_init(void);
void daemonize(void);
void sig_usr(int signo);
void destroy_scf(void);

int main(int argc, char **argv)
{
FILE *fp;
time_t t;

/* Read the app configuration here. */
/* If error occurred, return non-zero. */
if (read_config() != RUN_OK)
{
printf("%s: read configuration failuren", argv[0]);
exit(CONFIG_ERROR);
}

/* Initialize application. Any error, return non-zero. */
if (app_init() != RUN_OK)
{
printf("%s: initialization failuren", argv[0]);
exit(FATAL_ERROR);
}

daemonize(); /* Make the application a daemon. */

/* Service logic is placed here. */
while (1)
{
sleep(5); /* Sleep for 5 seconds. In a real application, it could be */
/* waiting for service requests, e.g. waiting on message */
/* queue, etc. */

/* service logic */
if ((fp = fopen(filename,"a")) != NULL)
{
t = time(0);
fprintf(fp, "myapp is running at %sn",asctime(localtime(&t)));
fclose(fp);
}
else
{
exit(FATAL_ERROR);
}
}
}

/* make the application a daemon */
void daemonize(void)
{
int pid;
int i;

if (pid = fork())
exit(RUN_OK); /* parent exits */
else if (pid < 0)
exit(FATAL_ERROR); /* fork() failed */

/* first child process */
setsid();
if (pid = fork())
exit(RUN_OK); /* first child exits */
else if(pid < 0)
exit(FATAL_ERROR); /* fork() failed */

/* second child */
for (i = 0; i < NOFILE; ++i) /* close all files */
close(i);

chdir("/"); /* change directory to root(/) directory */
umask(0); /* setup proper file mask */
}

/* read configurations in SMF repository */
int read_config(void)
{
memset(filename, 0, sizeof(filename));

/* connect to the current SMF global repository */
handle = scf_handle_create(SCF_VERSION);

/* allocate scf resources */
sc = scf_scope_create(handle);
svc = scf_service_create(handle);
pg = scf_pg_create(handle);
prop = scf_property_create(handle);
value = scf_value_create(handle);

/* if failed to allocate resources, exit */
if (handle == NULL || sc == NULL || svc == NULL ||
pg == NULL || prop == NULL || value == NULL)
{
destroy_scf();
return CONFIG_ERROR;
}

/* bind scf handle to the running svc.configd daemon */
if ((scf_handle_bind(handle)) == -1)
{
destroy_scf();
return CONFIG_ERROR;
}

/* get the scope of the localhost in the current repository */
if (scf_handle_get_scope(handle, SCF_SCOPE_LOCAL, sc) == -1)
{
destroy_scf();
return CONFIG_ERROR;
}

/* get the service of "application/myapp" within the scope */
if (scf_scope_get_service(sc, "application/myapp", svc) == -1)
{
destroy_scf();
return CONFIG_ERROR;
}

/* get the property group of "myapp" within the given service */
if (scf_service_get_pg(svc, "myapp", pg) == -1)
{
destroy_scf();
return CONFIG_ERROR;
}

/* get the propery of "log_filename" within the given property group */
if (scf_pg_get_property(pg, "log_filename", prop) == -1)
{
destroy_scf();
return CONFIG_ERROR;
}

/* get the value of the given property */
if (scf_property_get_value(prop, value) == -1)
{
destroy_scf();
return CONFIG_ERROR;
}

/* retrieve the value as a string */
if (scf_value_get_astring(value, filename, sizeof(filename)) == -1)
{
destroy_scf();
return CONFIG_ERROR;
}

/* free all resources */
destroy_scf();

return RUN_OK; /* OK */
}

/* free all allocated scf resources */
void destroy_scf(void)
{
if (value != NULL) scf_value_destroy(value);
if (prop != NULL) scf_property_destroy(prop);
if (pg != NULL) scf_pg_destroy(pg);
if (svc != NULL) scf_service_destroy(svc);
if (sc != NULL) scf_scope_destroy(sc);
if (handle != NULL) scf_handle_destroy(handle);
}

/* initialize application */
int app_init(void)
{
/* setup the SIGUSR1 signal handler */
signal(SIGUSR1, sig_usr);
return RUN_OK; /* OK */
}

/* signal handler for SIGUSR1 */
void sig_usr(int signo)
{
/* re-read the configuration when signal is received */
if (read_config() != RUN_OK) exit(CONFIG_ERROR);
}

在myapp.c程序中,read_config()就是读取保存在SMF全局资源库配置的过程。除了在main()过程第行服务启动时被调用外,还在sig_usr()过程中第行被调用。而sig_usr()过程在app_init()初始化过程中被设定为SIGUSR1信号的处理程序。这意味着,此服务在运行过程中只要收到SIGUSR1信号后,就会重新读取存在SMF资源库中的配置信息。

使用libscf.so的API读取SMF全局资源库/etc/svc/repository.db服务配置属性的一般流程如下图所示。


本例中,服务名字为application/myapp。属性组myapp内只有一个属性log_filename,它指定了日志文件的全路径文件名。由于此属性为字符型,所以使用scf_value_get_astring()将属性值value转换字符串置于filename。如果服务有更多的属性,可以多次执行循环1,甚至如果用户服务有多个属性组,可以多次执行循环2,直至将所有属性全部读出。

read_config()过程的第至118行逻辑是申请scf资源,其目的是先得到保存相关scf资源的内存。相应的destroy_scf()过程是释放scf资源。当配置读取完毕后,应将所有申请的scf资源释放。

本例只使用很少一部分libscf.so中的API调用,API调用列表可参考man libscf(3LIB)。API调用具体定义可以参考。有关SMF服务、属性组、属性等概念,以及SMF服务开发方面更多信息,请参阅Solaris Service Management Facility - Service Developer Introduction

利用Sun公司最新推出的C/C++/Fortran开发及编译环境Sun Studio 11,使用以下命令可将本例将myapp.c编译成可执行程序myapp。

$ /opt/SUNWspro/bin/cc -o ./myapp ./myapp.c

或者直接使用Solaris 10自带的gcc编译器将myapp.c编译成可执行程序myapp。

$ /usr/sfw/bin/gcc -o ./myapp ./myapp.c

编译成功后在当前目录下会生成myapp可执行程序。本例假设当前目录为/export/home/smfdemo。直接在命令行输入./myapp就可以启动myapp为后台守护进程。

启动脚本

启动脚本定义了如何启动myapp服务,以及与SMF相应的返回值。本例启动脚本如所示。

表2. /export/home/smfdemo/myapp.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/sbin/sh
###############################################################################
# /export/home/smfdemo/myapp.sh #
###############################################################################

. /lib/svc/share/smf_include.sh

RUN_OK=0
CONFIG_ERROR=1
FATAL_ERROR=2

case "$1" in
'start')
/export/home/smfdemo/myapp
if [ $? -eq $CONFIG_ERROR ]; then
exit $SMF_EXIT_ERR_CONFIG
fi
if [ $? -eq $FATAL_ERROR ]; then
exit $SMF_EXIT_ERR_FATAL
fi
;;

*)
echo "Usage: $0 start"
;;
esac

exit $SMF_EXIT_OK

manifest文件

SMF manifest文件定义了SMF服务各属性。比如,定义服务名称、服务依赖关系、服务启动方法、服务停止方法、服务刷新方法、服务所需参数等。本例实现了refresh方法,相应的manifest文件如所示。

表3. /export/home/smfdemo/myapp.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46





name='application/myapp'
type='service'
version='1'>





name='milestone'
grouping='require_all'
restart_on='none'
type='service'>



type='method'
name='start'
exec='/export/home/smfdemo/myapp_smf.sh start'
timeout_seconds='60' />

type='method'
name='stop'
exec=':kill'
timeout_seconds='60' />

type='method'
name='refresh'
exec=':kill -8'
timeout_seconds='60' />







本列中refresh方法执行变量被设为“:kill -8”,通过kill命令向服务发送SIGUSR1信号。与myapp.c中read_config()逻辑相对应,manifest文件中创建了 myapp属性组(property group)。它包含一个名为log_filename的字符型属性,初始值设为/tmp/myapp.log。有关manifest文件编写详细信息,请参看Solaris Service Management Facility - Service Developer Introduction

至此,使用libscf.so实现服务refresh方法的所有工作全部完成。下节中讲述如何将把myapp部署为SMF服务并作简单测试。

部署和测试

部署

管理和修改SMF服务分别需要solaris.smf.manage和solaris.smf.modify权限,具体请参看 smf_security(5)。缺省只有root有此权限,可使用root部署SMF服务。如果使用普通用户账号,则需要root将solaris.smf.manage和 solaris.smf.modify权限赋予相关用户。方法是在/etc/user_attr文件中加入授权记录。比如为用户hunter加入SMF管理和修改权限,则/etc/user_attr显示如下,其中粗斜体部分为hunter所需的权限。

# cat /etc/user_attr
#
# Copyright (c) 2003 by Sun Microsystems, Inc. All rights reserved.
#
# /etc/user_attr
#
# user attributes. see user_attr(4)
#
#pragma ident "@(#)user_attr 1.1 03/07/09 SMI"
#
adm::::profiles=Log Management
lp::::profiles=Printer Management
root::::auths=solaris.*,solaris.grant;profiles=Web Console Management,All;lock_after_retries=no
hunter::::auths=solaris.smf.manage,solaris.smf.modify

假设本例中开发目录和所有文件都位于/export/home/smfdemo目录下,则将本例部署为SMF服务的步骤如下:

1. 使用svccfg(1M)命令检查myapp.xml文件是否符合XML规范。如果没问题则不会有任何输出,否则根据出错提示修改myapp.xml。

# /usr/sbin/svccfg validate /export/home/smfdemo/myapp.xml

2. 使用svcs(1)命令看是否已存在名为myapp的服务。如有则必须修改在myapp.xml中定义的服务名,否则继续。

# /usr/bin/svcs application/myapp

3. 使用svccfg(1M)命令加载myapp.xml所定义的服务并自动启动服务。

# /usr/sbin/svccfg import /export/home/smfdemo/myapp.xml

4. 使用svcs(1)命令查看myapp服务状态。如状态为online,则说明部署已成功且已运行,否则参看出错原因以及SMF日志以确定问题所在,然后重复上文中相关的步骤后再试。

# /usr/bin/svcs -xv application/myapp

如果myapp部署成功,根据log_filename初始值设定,可以在/tmp目录下找到myapp.log文件。使用/usr/bin/tail -f /tmp/myapp.log命令可以查看日志输出。至此,myapp已经部署完毕。

测试

测试的目的是确认myapp的refresh方法可以正常工作。方法是通过svccfg(1M)命令将myapp服务log_filename属性值从 /tmp/myapp.log改为/tmp/myapp_new.log。然后用svcadm refresh命令通知myapp服务重读配置。如果在/tmp目录下新生成myapp_new.log,则说明refresh方法成功了。具体步骤如下:

1. 确认myapp已经启动并处于online状态,并/tmp/myapp.log每隔5秒就有内容输出。

# /usr/bin/svcs application/myapp
STATE STIME FMRI
online 10:53:57 svc:/application/myapp:default
# /usr/bin/tail -f /tmp/myapp.log
myapp is running at Fri Mar 10 10:57:12 2006

myapp is running at Fri Mar 10 10:57:17 2006

myapp is running at Fri Mar 10 10:57:22 2006

myapp is running at Fri Mar 10 10:57:27 2006

myapp is running at Fri Mar 10 10:57:32 2006

myapp is running at Fri Mar 10 10:57:37 2006

^C

2. 使用svccfg(1M)修改myapp服务myapp属性组中log_filename属性。

# /usr/sbin/svccfg
svc:>
select application/myapp
svc:/application/myapp>
listprop
general framework
general/single_instance boolean true
milestone dependency
milestone/entities fmri svc:/milestone/multi-user
milestone/grouping astring require_all
milestone/restart_on astring none
milestone/type astring service
start method
start/exec astring "/export/home/smfdemo/myapp.sh start"
start/timeout_seconds count 60
start/type astring method
stop method
stop/exec astring :kill
stop/timeout_seconds count 60
stop/type astring method
refresh method
refresh/exec astring ":kill -8"
refresh/timeout_seconds count 60
refresh/type astring method
myapp application
myapp/log_filename astring /tmp/myapp.log
svc:/application/myapp> setprop myapp/log_filename = "/tmp/myapp_new.log"
svc:/application/myapp> listprop
general framework
general/single_instance boolean true
milestone dependency
milestone/entities fmri svc:/milestone/multi-user
milestone/grouping astring require_all
milestone/restart_on astring none
milestone/type astring service
start method
start/exec astring "/export/home/smfdemo/myapp.sh start"
start/timeout_seconds count 60
start/type astring method
stop method
stop/exec astring :kill
stop/timeout_seconds count 60
stop/type astring method
refresh method
refresh/exec astring ":kill -8"
refresh/timeout_seconds count 60
refresh/type astring method
myapp application
myapp/log_filename astring /tmp/myapp_new.log
svc:/application/myapp> quit

3. 使用svcadm refresh命令通知myapp服务重读配置并启用新配置。

# /usr/sbin/svcadm refresh application/myapp
# /usr/bin/svcs application/myapp
STATE STIME FMRI
online 11:13:35 svc:/application/myapp:default

4. 检验myapp已经采用新配置。

# /usr/bin/tail -f /tmp/myapp_new.log
myapp is running at Fri Mar 10 11:14:35 2006

myapp is running at Fri Mar 10 11:14:40 2006

myapp is running at Fri Mar 10 11:14:45 2006

myapp is running at Fri Mar 10 11:14:50 2006

myapp is running at Fri Mar 10 11:14:55 2006

myapp is running at Fri Mar 10 11:15:00 2006

^C

至此,测试myapp服务refresh方法已经成功。

总结

Solaris 10操作系统是Sun公司最新的下一代操作系统,包含了600多项革新技术,SMF技术就是其中之一。通过使用SMF技术,系统中所有的服务可以在一个统一而强大的平台中进行配置和管理。通过将用户配置存放于SMF资源库,用户可以利用SMF配置工具svccfg(1M)方便地维护服务配置。另外,libscf.so所提供的API也可以使服务程序省去大部分复杂的配置读取和解析逻辑。

参考资料

1.
2.
3.
4. Solaris 10操作系统/usr/share/lib/xml/dtd/service_bundle.dtd文件
5. Solaris 10操作系统上,以下man页面:

  • libscf(3LIB)
  • svc.startd(1M)
  • svc.configd(1M)
  • svcadm(1M)
  • svccfg(1M)
  • svcprop(1)
  • svcs(1)
阅读(3055) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

Hxcan_cu2010-12-06 16:12:45

请问一下SMF是不是Solaris独有的东西?