存档

‘开发’ 分类的存档

学习PAM模块开发

2010年11月13日 16hot 没有评论

原文地址: http://www.ibm.com/developerworks/cn/linux/l-pamdev/index.html

最近因为在处理SSHD登录,需要用到PAM模块,系统自带的没有能满足需求。只好自己照猫画老虎,模仿学习着写了个pam模块,工作很正常。pam确实很不错的一种认证技术。

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

本文主要通过对Linux PAM源代码进行分析,阐述了PAM的内部实现机制和怎样在应用程序中应用PAM进行认证,以及怎样开发PAM服务模块。

1 引言

身份认证是操作系统安全的重要机制之一,系统通过认证机制核查用户的身份证明,并作为用户进入系统的判定条件,是防止恶意用户进入系统 的第一道门槛。近年来认证理论和技术得到了迅速发展,产生了各种认证机制,如口令机制,RSA, DCE, kerberos认证体制,S/Key和基于智能卡的身份认证等。然而,当系统中引入新的认证机制时,一些系统入口登录服务如login, rlogin和telnet等应用程序就必须改写以适应新的认证机制。为了解决这个问题,1995年Sun公司的Vipin Samar和 Charlie Lai提出了PAM(Pluggable Authentication Modules),并将其应用在Solaris系统上。PAM框架将应用程序与具体的认证机制分离,使得系统改变认证机制时,不再需要修改采用认证机制的 应用程序,而只要由管理员配置应用程序的认证服务模块,极大地提高了认证机制的通用性与灵活性。

现在大多数操作系统都采用PAM实现身份认证,有Linux系统的Linux-PAM和FreeBSD5.x采用的OpenPAM(FreeBSD 4.x采用的是Linux-PAM)等。它们的实现原理一样,只有实现细节不同而已。下面从PAM的应用开发开始介绍。


回页首

2 PAM的应用开发

2.1 PAM框架概览

PAM即可插拔认证模块。它提供了对所有服务进行认证的中央机制,适用于login,远程登录 (telnet,rlogin,fsh,ftp,点对点协议(PPP)),su等应用程序中。系统管理员通过PAM配置文件来制定不同应用程序的不同认证 策略;应用程序开发者通过在服务程序中使用PAM API(pam_xxxx( ))来实现对认证方法的调用;而PAM服务模块的开发者则利用PAM SPI来编写模块(主要是引出一些函数pam_sm_xxxx( )供PAM接口库调用),将不同的认证机制加入到系统中;PAM接口库(libpam)则读取配置文件,将应用程序和相应的PAM服务模块联系起来。 PAM框架结构如图所示。
图 PAM框架结构图
图 PAM框架结构图

其中,pamh是一个pam_handle类型的结构,它是一个非常重要的处理句柄,是PAM与应用程序通信的唯一数据结构,也是调用PAM接口库API的唯一句柄。pam_handle数据结构将在下面的源代码分析一节的介绍。

另外,如上图所示的服务模块分auth(认证管理)、account(账号管理)、session(会话管理)、passwd(口令管 理)四种类型,各个类型模块的作用以及配置文件的四个组成部分模块类型、控制标志、模块路径、模块参数等在很多讲PAM的配置管理的文章里都有介绍,这里 就不再赘述了。

2.2 在应用程序中使用PAM认证

每个使用PAM认证的应用程序都以pam_start开始,pam_end结束。PAM还提供了pam_get_item和 pam_set_item共享有关认证会话的某些公共信息,例如用户名,服务名,密码和会话函数。应用程序在调用了pam_start ()后也能够用这些APIs来改变状态信息。实际做认证工作的API函数有六个(以下将这六个函数简称为认证API):

  • 认证管理–包括pam_authenticate ()函数认证用户,pam_setcred ()设置,刷新,或销毁用户证书。
  • 账号管理–包括pam_acc_mgmt ()检查认证的用户是否可以访问他们的账户,该函数可以实现口令有效期,访问时间限制等。
  • 会话管理–包括pam_open_session ()和pam_close_session ()函数用来管理会话和记账。例如,系统可以存储会话的全部时间。
  • 口令管理–包括pam_chauthok ()函数用来改变密码。

下面看一个简单的login模拟程序:

 /* 使用PAM所必需的两个头文件*/
#include <security/pam_appl.h>
#include <security/pam_misc.h>
void main(int argc, char *argv[], char **renvp)
{
    /* 初始化,并提供一个回调函数 */
    if ((pam_start("login", user_name, &pam_conv, &pamh)) != PAM_SUCCESS)
        exit(1);
    /* 设置一些关于认证用户信息的参数 */
    pam_set_item(pamh, PAM_TTY, ttyn);
    pam_set_item(pamh, PAM_RHOST, remote_host);
    while (!authenticated && retry < MAX_RETRIES)
    {
        status = pam_authenticate(pamh, 0);/* 认证,检查用户输入的密码是否正确 */
}
/* 认证失败则应用程序退出*/
    if (status != PAM_SUCCESS)
    {
      	……
        exit(1);
}
    /*  通过了密码认证之后再调用帐号管理API,检查用户帐号是否已经过期 */
    if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS)
    {
        if (status == PAM_AUTHTOK_EXPIRED)
        {
            status = pam_chauthtok(pamh, 0);  /* 过期则要求用户更改密码 */
            if (status != PAM_SUCCESS)
                exit(1);
        }
    }
    /* 通过帐户管理检查之后则打开会话 */
    if (status = pam_open_session(pamh, 0) != PAM_SUCCESS)
        exit(status);
	……
    /* 建立认证服务的用户证书*/
    status = pam_setcred(pamh, PAM_ESTABLISH_CRED);
    if (status != PAM_SUCCESS)
       exit(status);
   	……
pam_end(pamh, PAM_SUCCESS);  /* PAM事务的结束 */
……
 }

从上面程序中,我们可以了解到使用PAM认证的一般流程,同时也可以看出PAM API使得使用认证的应用程序不仅不用关心底层使用的服务模块,而且编写起来简洁明了得多。

有关开发使用PAM的应用程序更加详细完整的阐述请参考The Linux-PAM Application Developers’ Guide。

2.3 怎样开发PAM服务模块

首先在编写的服务模块的源程序里要包含下列头文件:

#include <security/pam_modules.h>

PAM的服务模块是一个一个的动态链接库文件(也可以是静态库),PAM接口库通过dlopen来装载这些库。假设源程序名为pam_module-name.c,则需要用下列命令将其编译成动态链接库:

gcc -fPIC -c pam_module-name.c
ld -x --shared -o pam_module-name.so pam_module-name.o

选项-fPIC是指位置无关代码(Position Independent Code),这类代码支持大偏移。使用–shared选项将目标代码放进共享目标库中。

四种类型的模块各自要实现的函数如下表所示:

模块类型 要实现的函数 函数功能
认证管理 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) 认证用户
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 设置用户证书
账号管理 PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) 帐号管理
会话管理 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 打开会话
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 关闭会话
口令管理 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) 设置口令

当然同一个服务模块可以同时属于多种类型,只要这些类型模块要实现的函数都实现了就可以,比如PAM自带的经典口令认证机制模块pam_unix.so 就可以支持四种模块类型。

下面来看一个最简单的pam_deny模块的源程序pam_deny.c:

1.	#define PAM_SM_AUTH
2.	#define PAM_SM_ACCOUNT
3.	#define PAM_SM_SESSION
4.	#define PAM_SM_PASSWORD
5.	#include "../../libpam/include/security/pam_modules.h"
6.	/* --- 认证管理函数的实现--- */
7.	PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc
8.	,const char **argv)
9.	{
10.	    return PAM_AUTH_ERR;
11.	}
12.	PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc
13.	,const char **argv)
14.	{
15.	    return PAM_CRED_UNAVAIL;
16.	}
17.	/* --- 账号管理函数的实现 --- */
18.	PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc
19.	,const char **argv)
20.	{
21.	    return PAM_ACCT_EXPIRED;
22.	}
23.	/* --- 口令管理函数的实现 --- */
24.	PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh,int flags,int argc
25.	,const char **argv)
26.	{
27.	    return PAM_AUTHTOK_ERR;
28.	}
29.	/* --- 会话管理函数的实现 --- */
30.	PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh,int flags,int argc
31.	,const char **argv)
32.	{
33.	    return PAM_SYSTEM_ERR;
34.	}
35.	PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc
36.	,const char **argv)
37.	{
38.	    return PAM_SYSTEM_ERR;
39.	}
40.	/* 模块定义结束 */
41.	/* 静态模块数据 */
42.	#ifdef PAM_STATIC
43.	struct pam_module _pam_deny_modstruct = {
44.	   "pam_deny",
45.	    pam_sm_authenticate,
46.	    pam_sm_setcred,
47.	    pam_sm_acct_mgmt,
48.	    pam_sm_open_session,
49.	    pam_sm_close_session,
50.	    pam_sm_chauthtok
51.	};
52.	#endif

很容易看出,pam_deny模块支持四种模块类型。前4行包含静态模块的一些原型申明,第37-39行实现了四种模块类型的函数,因 为这只是一个简单的拒绝服务的模块,所以这些函数只是简单地返回认证错或系统错等PAM错误。最后几行定义了该程序被编译成静态模块所需的一个模块数据结 构。

因此,PAM SPI使得服务模块的开发也相当简单和专一,因为服务模块不再需要考虑和应用程序的交互,只要将自己采用的算法实现好就可以了。

模块源程序可用的flags参数值和返回值的定义这里不作全面介绍,有兴趣者请参考The Linux-PAM Module Writers’ Guide。


回页首

3 PAM接口库源代码分析

上面我们介绍了怎样使用PAM和怎样开发PAM服务模块,要想对PAM的内部机制有个透彻的理解,还需要进一步分析PAM接口库的代码。下面基于Linux 的pam-0.75-40.src.rpm包所得的源代码进行分析。

3.1 PAM接口库主要数据结构

先看一下PAM接口库用到的一些主要数据结构。pam_handle和其他几个主要的数据结构(见../libpam/pam_private.h)及其之间的关系如下图所示。

其中pam_handle包含认证的用户的token、用户名、应用程序名、终端名等信息,以及一个service结构(handlers);前面几节提到的pamh句柄就是一个pam_handle结构。service结构包含服务模块的相关信息,各个域的含义是:

1. module–该结构包含装载的模块的名字、类型(静态或动态模块)、链接句柄(装载模块时的句柄)。

2. modules_allocated–分配的模块数。

3. modules_used–已使用的模块数。

4. handlers_loaded–是否对操作(handlers结构)进行了初始化,handlers结构和初始化handlers见下面的介绍。

5. conf–由应用程序相对应的配置文件指定的服务模块的handlers。

6. other–为缺省配置文件指定的服务模块的handlers。

handlers结构包含六个handler结构链表的指针,六个指针分别对应六种不同的认证API;libpam通过这些指针找到对应模块的SPI服务函数。如下表所示:

handler指针 API函数 SPI函数
authenticate pam_authenticate( ) pam_sm_authenticate( )
setcred pam_setcred( ) pam_sm_setcred( )
acct_mgmt pam_acct_mgmt( ) pam_sm_acct_mgmt( )
open_session pam_open_session( ) pam_sm_open_session( )
close_session pam_close_session( ) pam_sm_close_session( )
chauthtok pam_chauthtok( ) pam_sm_chauthtok( )

handler数据结构是最直接保存服务模块的SPI服务函数的地址及参数的结构,其包含的主要的域的含义如下:

1. (*func)–该函数指针指向handlers所装载的服务模块的服务函数。

2. argc、**argv–分别为*func所指向的函数的参数个数和参数列表。

3. *next–指向堆栈模块中的下一个服务模块的服务函数。由此指针形成所有堆栈模块的服务函数链。

3.2 PAM接口库重要内部函数分析

PAM接口库中有一系列_pam开头的内部函数,那些APIs主要是调用这些内部函数来完成其功能的。

int _pam_add_handler(pam_handle_t *pamh, int must_fail, int other, int type, int *actions, const char *mod_path, int argc, char **argv, int argvlen)

该函数负责加载服务模块的SPI函数。这个函数代码很长有300多行,这里就不列举了。其主要步骤如下:

1. 根据mod_path提供的模块路径装载服务模块(dl_open)。

2. 由type确定的类型来决定要装入的SPI函数名并找到该函数(dlsym)的地址。type类型对应配置文件中的服务模块的四种类型标记,type的值及其对应的要装入的SPI函数名见下表。

模块类型 type SPI函数
认证管理模块 PAM_T_AUTH pam_sm_authenticate
pam_sm_setcred
会话管理模块 PAM_T_SESS pam_sm_open_session
pam_sm_close_session
帐号管理模块 PAM_T_ACCT pam_sm_acct_mgmt
口令管理模块 PAM_T_PASS pam_sm_chauthtok

3. 新分配一个handler结构,将dlsym找到的函数的地址赋给该handler结构的func域,并填充其他的结构信息。

4. 将新分配的handler结构插入到handlers的对应handler结构链表中。

_pam_parse_conf_file函数负责读并分析PAM配置文件,将相关的信息填充到pamh句柄中,并调用_pam_add_handlers加载服务模块的服务函数。

_pam_init_handlers函数主要做handler的初始化工作,先判断handler是否已初始化,若没有则调用_pam_parse_conf_file分析配置文件加载服务模块的服务函数。

_pam_dispatch_aux(pam_handle_t *pamh, int flags, struct handler *h,……)

该函数负责遍历执行模块堆栈中的每一个服务模块对应的SPI函数,即执行h指向的handler结构链表中的每一个func指向的函数,并返回模块堆栈的结果值。

int _pam_dispatch(pam_handle_t *pamh, int flags, int choice)

该函数首先通过调用_pam_init_handlers将模块调度请求转换为指向实际要运行的模块堆栈函数链表的指针,并将该指针传 递给_pam_dispatch_aux函数来遍历模块堆栈执行服务函数。该函数是实现六个认证API函数的主要部分和公共调用的函数,通过choice 选项来区分是哪个认证API函数。

下面分析我们最关心的部分,也即那些认证API是怎样找到和调度配置文件中配置的服务模块的?

3.3 PAM认证API的实现

为了更加清楚地说明PAM接口库是怎样来调度使用模块的,下面给出了pam_authenticate函数执行的流程图:

其他的认证API函数(pam_open_session等)执行过程前面五个步骤同上图,只是在最后一步时传递给_pam_dispatch_aux的指针参数不同,传递3.1节表中每个API函数相对应的那个handler型指针,然后执行相对应的SPI服务函数链。


回页首

4 小结

无论从PAM的应用开发还是它的实现原理来看,这个框架及其思想都是非常完美的,所以几乎各种版本的 UNIX 系统都提供对 PAM 的支持。本文通过对Linux-PAM 进行了深入仔细的分析,阐述了它的内部实现机制,并且讲述了怎样在应用程序中使用PAM和怎样开发PAM服务模块,希望能对大家做认证相关的开发工作有所 帮助。

参考资料

  1. [Vipin Samar, Charlie Lai, 1995]
    Making Login Services Independent of Authentication Technologies. Sun Technical report.
  2. [Andrew G. Morgan, 2001]
    The Linux-PAM Module Writers’ Guide. Linux-PAM Documentation.
  3. [Andrew G. Morgan, 2001]
    The Linux-PAM Module Writers’ Guide. Linux-PAM Documentation.

关于作者

程卫芳,国防科技大学计算机学院在读硕士,研究方向为安全操作系统。 Email: desertbow@sina.com

分类: BSD/linux, C/C++, 转载 标签: , ,

Bash Shell中命令行选项/参数处理

2010年11月11日 16hot 没有评论

转自: http://www.cnblogs.com/FrankTan/archive/2010/03/01/1634516.html

0.引言

写程序的时候经常要处理命令行参数,本文描述在Bash下的命令行处理方式。

选项与参数:

如下一个命令行:

./test.sh -f config.conf -v –prefix=/home

我们称-f为选项,它需要一个参数,即config.conf, -v 也是一个选项,但它不需要参数。

–prefix我们称之为一个长选项,即选项本身多于一个字符,它也需要一个参数,用等号连接,当然等号不是必须的,/home可以直接写在–prefix后面,即–prefix/home,更多的限制后面具体会讲到。
在bash中,可以用以下三种方式来处理命令行参数,每种方式都有自己的应用场景。

* 手工处理方式
* getopts
* getopt

下面我们依次讨论这三种处理方式。

1. 手工处理方式

在手工处理方式中,首先要知道几个变量,还是以上面的命令行为例:

*    $0 : ./test.sh,即命令本身,相当于C/C++中的argv[0]
*    $1 : -f,第一个参数.
*    $2 : config.conf
*    $3, $4 … :类推。
*    $#  参数的个数,不包括命令本身,上例中$#为4.
*    $@ :参数本身的列表,也不包括命令本身,如上例为 -f config.conf -v –prefix=/home
*    $* :和$@相同,但”$*” 和 “$@”(加引号)并不同,”$*”将所有的参数解释成一个字符串,而”$@”是一个参数数组。如下例所示:

1 #!/bin/bash
2
3 for arg in ”$*”
4 do
5     echo $arg
6 done
7
8 for arg in ”$@”
9 do
10     echo $arg
11 done
12

执行./test.sh -f config.conf -n 10 会打印:

-f config.conf -n 10    #这是”$*”的输出

-f   #以下为$@的输出

config.conf

-n

10

所以,手工处理的方式即对这些变量的处理。因为手工处理高度依赖于你在命令行上所传参数的位置,所以一般都只用来处理较简单的参数。如

./test.sh 10

而很少使用./test -n 10这种带选项的方式。 典型用法为:

#!/bin/bash

if [ x$1 != x ]
then
#…有参数
else
then
#…没有参数
fi

为什么要使用 x$1 != x 这种方式来比较呢?想像一下这种方式比较:

if [ -n $1 ]  #$1不为空

但如果用户不传参数的时候,$1为空,这时 就会变成 [ -n ] ,所以需要加一个辅助字符串来进行比较。

手工处理方式能满足大多数的简单需求,配合shift使用也能构造出强大的功能,但在要处理复杂选项的时候建议用下面的两种方法。

2. getopts/getopt

处理命令行参数是一个相似而又复杂的事情,为此,C提供了getopt/getopt_long等函数,
C++的boost提供了Options库,在shell中,处理此事的是getopts和getopt.

getopts和getopt功能相似但又不完全相同,其中getopt是独立的可执行文件,而getopts是由Bash内置的。

先来看看参数传递的典型用法:

* ./test.sh -a -b -c  : 短选项,各选项不需参数
* ./test.sh -abc   : 短选项,和上一种方法的效果一样,只是将所有的选项写在一起。
* ./test.sh -a args -b -c :短选项,其中-a需要参数,而-b -c不需参数。
* ./test.sh –a-long=args –b-long :长选项

我们先来看getopts,它不支持长选项。

使用getopts非常简单:
代码

#test.sh

#!/bin/bash

while getopts ”a:bc” arg #选项后面的冒号表示该选项需要参数
do
case $arg in
a)
echo ”a’s arg:$OPTARG” #参数存在$OPTARG中
;;
b)
echo ”b”
;;
c)
echo ”c”
;;
?)  #当有不认识的选项的时候arg为?
echo ”unkonw argument”
exit 1
;;
esac
done

现在就可以使用:
./test.sh -a arg -b -c

./test.sh -a arg -bc
来加载了。
应该说绝大多数脚本使用该函数就可以了,如果需要支持长选项以及可选参数,那么就需要使用getopt.
下面是getopt自带的一个例子:

#!/bin/bash

# A small example program for using the new getopt(1) program.
# This program will only work with bash(1)
# An similar program using the tcsh(1) script language can be found
# as parse.tcsh

# Example input and output (from the bash prompt):
# ./parse.bash -a par1 ’another arg’ –c-long ’wow!*\?’ -cmore -b ” very long ”
# Option a
# Option c, no argument
# Option c, argument `more’
# Option b, argument ` very long ’
# Remaining arguments:
# –> `par1′
# –> `another arg’
# –> `wow!*\?’

# Note that we use `”$@”‘ to let each command-line parameter expand to a
# separate word. The quotes around `$@’ are essential!
# We need TEMP as the `eval set –’ would nuke the return value of getopt.

#-o表示短选项,两个冒号表示该选项有一个可选参数,可选参数必须紧贴选项
#如-carg 而不能是-c arg
#–long表示长选项
#”$@”在上面解释过
# -n:出错时的信息
# – :举一个例子比较好理解:
#我们要创建一个名字为 ”-f”的目录你会怎么办?
# mkdir -f #不成功,因为-f会被mkdir当作选项来解析,这时就可以使用
# mkdir – -f 这样-f就不会被作为选项。

TEMP=`getopt -o ab:c:: –long a-long,b-long:,c-long:: \
-n ’example.bash’ – ”$@”`

if [ $? != 0 ] ; then echo ”Terminating…” >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP’: they are essential!
#set 会重新排列参数的顺序,也就是改变$1,$2…$n的值,这些值在getopt中重新排列过了
eval set – ”$TEMP”

#经过getopt的处理,下面处理具体选项。

while true ; do
case ”$1″ in
-a|–a-long) echo ”Option a” ; shift ;;
-b|–b-long) echo ”Option b, argument \`$2′” ; shift 2 ;;
-c|–c-long)
# c has an optional argument. As we are in quoted mode,
# an empty parameter will be generated if its optional
# argument is not found.
case ”$2″ in
“”) echo ”Option c, no argument”; shift 2 ;;
*)  echo ”Option c, argument \`$2′” ; shift 2 ;;
esac ;;
–) shift ; break ;;
*) echo ”Internal error!” ; exit 1 ;;
esac
done
echo ”Remaining arguments:”
for arg do
echo ’–> ’”\`$arg’” ;
done

比如我们使用
./test -a  -b arg arg1 -c
你可以看到,命令行中多了个arg1参数,在经过getopt和set之后,命令行会变为:
-a -b arg -c — arg1
$1指向-a,$2指向-b,$3指向arg,$4指向-c,$5指向–,而多出的arg1则被放到了最后。

3.总结

一般小脚本手工处理也许就够了,getopts能处理绝大多数的情况,getopt较复杂,功能也更强大。
有问题请指出,不胜感激。

分类: Shell, 转载 标签:

线程模式下工作

2010年11月8日 16hot 没有评论

这几天将memcached的线程模式移植到ednsd来,性能有所提升,但是没有以为中的高倍提升。下面是不连redis的统计。

Queries per second:   51600.484825 qps
Queries per second:   51664.028522 qps
Queries per second:   49371.465948 qps
Queries per second:   48488.016842 qps
Queries per second:   46953.006773 qps

Queries sent:         4485137 queries
Queries completed:    4485137 queries

经过测试数据表明,不是开的线程越多,性能越高,而是跟CPU(或者内核)数对应,性能最高。比如4核的CPU,4个线程,性能是最优的。另外,在10万个数据请求时,只有3.6万QPS。在440万个数据请求时,能上到5万QPS左右。

另一方面也说明了,线程模式在高负载的情况下,才能发挥性能。否则有点杀鸡用牛刀之嫌。

分类: C/C++, DNS 标签:

判断两个链表有无交点,如果有请给出交点

2010年11月4日 16hot 没有评论
  1. /*
  2. * if list_a and list_b has cross point return the addrss of cross-point.
  3. * else return NULL
  4. */
  5. static List *has_cross(List *list_a, List *list_b)
  6. {
  7. List    *pa;
  8. List    *pb;
  9. int     len_a, len_b;
  10. int     i;
  11. len_a = len_b = 0;
  12. pa = list_a; /* 遍历链表a,并记录下此链表的长度 */
  13. while (pa->next != NULL) {
  14. pa = pa->next;
  15. len_a++;
  16. }
  17. pb = list_b; /* 遍历链表b,并记录下链表b的长度 */
  18. while (pb->next != NULL) {
  19. pb = pb->next;
  20. len_b++;
  21. }
  22. if (pa != pb) /* 如果指针pa,pb最后不相等,则两链表没有焦点 */
  23. return NULL;
  24. /* 较长的链表先向后调整到第N个元素(N = max(len_a, len_b) - min(len_a, len_b))
  25. 然后两链表同步向后调整,同时比较地址是否相等,如果相等则为首焦点。*/
  26. pa = list_a;
  27. pb = list_b;
  28. if (len_a > len_b) {
  29. for (i = len_a - len_b; i; i–)
  30. pa = pa->next;
  31. } else {
  32. for (i = len_b - len_a; i; i–)
  33. pb = pb->next;
  34. }
  35. while (pa != pb) {
  36. pa = pa->next;
  37. pb = pb->next;
  38. }
  39. return pa;
  40. }
分类: C/C++, 转载 标签:

删除C程序中的注释

2010年11月4日 16hot 没有评论
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main(int argc, char **argv)
  5. {
  6. char start;
  7. char end;
  8. char cur;
  9. start = end = ’\0′;
  10. while ((cur = getchar()) != EOF) {
  11. if (cur == ’/' && start == ’/') {
  12. while ( (cur = getchar()) != EOF && cur != ’\n’) {
  13. start = cur;
  14. }
  15. start = ’\0′;
  16. } else if (start == ’/' && cur == ’*') {
  17. while ( (cur = getchar()) != EOF) {
  18. if (end == ’*' && cur == ’/') {
  19. start = end = cur = ’\0′;
  20. break;
  21. }
  22. end = cur;
  23. }
  24. }
  25. if (start != ’\0′)
  26. putchar(start);
  27. start = cur;
  28. }
  29. putchar(start);
  30. exit(EXIT_SUCCESS);
  31. }
分类: C/C++, 转载 标签:

学习管道pipe应用

2010年10月29日 16hot 没有评论

今天学习了pipe在进程和线程下的工作。记录一下。

int pfd[2];
if ( pipe(pfd) < 0 ) {
//出错
}

// pfd[0] 是读,pfd[1] 是写

在线程中使用管道。

void cb_func(evutil_socket_t fd, short what, void *arg)
{
const char *data = arg;

char buf_rr[ EDNSD_LOG_MAX_SIZE * 2 + 1 ];
if(read( fd, buf_rr, EDNSD_LOG_MAX_SIZE * 2 ) > 0 ) {
printf(“BUF_R: in child child process,read from the pipe is %s\n”,buf_rr);
}
}

static void
ednsd_log_server( int log_rfd )
{
struct event_base *event_base = NULL;
event_base = event_base_new();

struct event  *event;

event = event_new(event_base, log_rfd, EV_READ |EV_PERSIST, cb_func,
(char*)”Reading event”);

event_add(event, NULL);
event_base_dispatch(event_base);

}

void ednsd_log_initd()
{
int log_pfd[2];
pthread_t thread_id;
pthread_attr_t  attr;

/**
* 创建日志线程
*/

if ( pipe(log_pfd) < 0 ) {
syslog( LOG_CRIT, “pipe error” );
}

pthread_attr_init(&attr);
pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );

pthread_create( &thread_id, &attr, ednsd_log_server, log_pfd[0] );

pthread_attr_destroy(&attr);
ednsd_config.log_fd = log_pfd[1];

}

分类: C/C++ 标签: , ,

管道(PIPE)

2010年10月28日 16hot 没有评论
进程通信(IPC)的几种方式及比较
撰文:黄显国080416
难得闲暇,抽空学习了一下进程通信的知识,现将这几天的所学做一下总结,以备遗忘时参考。
进程通信的方式:
Linux系统继承了三种系统的进程通信模式:
1、 基于system V IPC
2、 基于UNIX IPC
3、 基于POSIX IPC
同时还包含一种socket进程间通信,不过这种是不同处理器系统之间的一种网络通信方式,不是我所关心的。
方式一:管道(PIPE
管道分无名管道与有名管道两种
1、 无名管道。
无名管道用于具有亲缘关系的父子进程,子子进程之间的通讯。它的实现函数有
int pipe(int fd[2]);
//fd[2]为描述符数组,包含一个读描述符与一个写描述符,在使用管道通信时,关闭某些不需要的读或写描述符,建立起单向的读或写管道,然后用readwrite像操作文件一样去操作它即可。
如图便是进程1到进程2的一个读管道。
以下是我写的一个pipe的验证程序,分别在父进程和父子进程里向管道写数据,然后在子进程和子子进程里读数据,当尝试改变各子进程的sleep时间以实现渴望的同步时,会发现结果很有趣。注意创建子进程时将复制父进程的管道。
/*******************************************************************************************/
//pipe.c
//frome the example, we can see:
//pipe创建的无名管道,父子进程,子子进程之间都可以通信,由于read
//write默认为阻塞,而进程与进程之间又有某种意义上的同步方法;
//故而可以从下面的程序中得到一些启示。
/*****************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main()
{
int pipe_fd[2];
char buf_r[100];
char buf_rr[100];
if(pipe(pipe_fd)<0)
{
printf(“pipe create error\n”);
return -1;
}
else
printf(“pipe create success\n”);
printf(“pipe_fd[0]=%d,pipe_fd[1]=%d\n”,pipe_fd[0],pipe_fd[1]);
if(fork()==0)//子进程
{
close(pipe_fd[1]);//关闭子进程的写描述符
printf(“fork()”);
//sleep(2);
if(fork()==0)//子子进程
{
if(read(pipe_fd[0],buf_r,5)>0)
printf(“BUF_R: in child child process,read from the pipe is %s\n”,buf_r);
close(pipe_fd[0]);
exit(0);
}
Else //子进程
{
if(read(pipe_fd[0],buf_rr,3)>0)
printf(“BUF_RR: in child parent process,read from the pipe is %s\n”,buf_rr);
close(pipe_fd[0]);
exit(0);
}
}
else  //父进程
{
close(pipe_fd[0]);
sleep(5);
if(write(pipe_fd[1],”Hello “,5)!=-1)
printf(“write1 parent pipe success\n”);
if(fork()==0)  //父子进程
{
if(write(pipe_fd[1],”PIPE”,5)!=-1)
printf(“write parent child pipe success”);
close(pipe_fd[1]);
exit(0);
}
close(pipe_fd[1]);
exit(0);
}
}
2、 有名管道
有名管道可用于两个无关的进程之间的通信。它的实现函数是:
int mkfifo(const char *filename, mode_t mode)
//创建一个名为filename的管道,模式可选为读或写方式,阻塞或非阻塞方式等。
下面一个实例演示了mkfifo的使用。Fifo_read.c不断从管道文件里读数据,fifo_write.c往管道文件里写数据。改变sleep的值也会产生类似上面进程同步的问题,而会发现一些缓冲区的特性。
两个程序用gcc编译后在两个终端里运行。
//—————————————————————————————————
//fifo_read.c
//创建有名管道,演示两个不相关的进程之间的通信
//int mkfifo(const char *filename, mode_t mode)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#define FIFO “/home/huang/myfifo”
int main(int argc,char **argv)
{
int fd;
int nread;
char buf_r[100];
if(mkfifo(“/home/huang/myfifo”,O_CREAT|O_EXCL)<0)//&&(errno!=EEXIST))
{
perror(“mkfifo:”);
printf(“cann’t create fifoserver\n”);
return -1;
}
printf(“Preparing for reading bytes…\n”);
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
if(fd==-1)
{
perror(“open!\n”);
exit(1);
}
while(1)
{
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,sizeof(buf_r)))==-1)
{
if(errno==EAGAIN)
printf(“no data yet\n”);
}
printf(“read %s from FIFO\n”,buf_r);
sleep(1);
}
pause();
unlink(FIFO);
}
//——————————————————————————————————————
//fifo_write.c
//创建有名管道,演示两个不相关的进程之间的通信
//int mkfifo(const char *filename, mode_t mode)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#define FIFO “/home/huang/myfifo”
int main(int argc, char **argv)
{
int fd;
char w_buf[100];
int nwrite;
if(argc==1)
{
printf(“please send some message\n”);
exit(1);
}
fd=open(FIFO,O_WRONLY|O_NONBLOCK,0);
if(fd==-1)
{
if(errno==ENXIO)
printf(“open error;no reading process\n”);
perror(“open:”);
return -1;
}
memset(w_buf,’a',sizeof(w_buf));
printf(“sizeof(w_buf)=%d\n”,sizeof(w_buf));
while(1)
{
if((nwrite=write(fd,w_buf,strlen(w_buf)))==-1)
{
if(errno==EAGAIN)
printf(“The FIFO has not been write yet.\n”);
perror(“write”);
//     else
//     printf(“error in writting!\n”);
}
else
printf(“write %s to the FIFO\n”,w_buf);
sleep(2);
}
close(fd);
}
分类: BSD/linux, C/C++, 转载 标签: ,

DNS查询统计图

2010年10月23日 16hot 没有评论

采用rrdtool生成的统计图,可以点击查看完整尺寸图片。

1、总统计图

2、记录类型统计图

分类: DNS, PHP 标签:

DNS报文格式

2010年10月22日 16hot 没有评论
Query Identifier(16) QR OPCodes Flags Reserved RCodes
QDCount(16) ANCount(16)
NSCount(16) ARCount(16)
Question Section(32)
Answer Section(32)
Authority Section(32)
Additional Records Section(32)

以下为各栏位的简要说明:
QID
DNS 查询封包编号,作为确认依据。
QR
查询封包为 0 ﹔回应为 1 。长度为 1 byte 。
OPCodes
封包类别(QUERY, IQUERY, STATUS, Reserved)。长度为 4 bytes。
Flags
共 4 bytes ,各表示:AA(Authoritative Answer)、TC(Truncation)、RD(Recursion Desired)、RA(Recursion Avalable)。
Reserved
保留未用。
RCodes
回应讯息,长 4 bytes ,除 0 及 6-15 保留未用外,1-5 分别为:Format Error、Server Failure、Name Error、Not Implemented、Refused。
Question Section、Answer Section、Authority Section、Additional Records Section
每一 Section 分为 NAME、TYPE、CLASS 三个子栏位,分别作为查询、应答、授权、额外记录等封包之资讯,及各自长度。
好了﹐关于 DNS 协定的讨论﹐暂时介绍到这里﹐相信上面说的这些也够您消化的了。
DNS 协定之 RFC 文件
RFC-822﹑RFC-883﹑RFC-920﹑RFC-973﹑RFC-974﹑RFC-1032﹑RFC-1033﹑RFC-1034﹑RFC-1035﹑RFC-1101﹑RFC-1296

分类: C/C++, DNS 标签:

libevent book

2010年10月18日 16hot 没有评论
分类: C/C++ 标签: