Chinaunix首页 | 论坛 | 博客
  • 博客访问: 417001
  • 博文数量: 380
  • 博客积分: 75
  • 博客等级: 民兵
  • 技术积分: 1925
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-05 15:35
文章分类

全部博文(380)

文章存档

2014年(1)

2013年(2)

2012年(19)

2011年(358)

我的朋友

分类:

2011-09-24 10:50:48

1.   信号基本概念

信号(signal)是linux进程通讯中唯一的异步通讯方式。

信号从软件层次上看是对中断机制的一种模拟。一个进程收到信号时的处理方式与CPU收到中断请求时的处理方式一样。收到信号的进程会跳入信号处理函数,执行完后再跳回原来的位置继续执行。

信号来源:有一类信号是已经被定义好的,如数据异常、指令异常、定时器、abort等。他们都有自己特殊的用法,如:发生异常时会触发异常信号。还有一类是自定义信号,

 

2.   信号分类 2.1.  可靠与不可靠

Linux中的信号有64个,分为可靠信号与不可靠信号两种。

不可靠信号:

Linux信号机制来继承自Unix系统,信号值小于SIGRTMIN(SIGRTMIN=32SIGRTMAX=63)的信号都沿用了Unix的实现方式,这种方式的信号可能会丢失,所以称为不可靠信。不可靠信号的处理机制类似于中断,同一个信号同时发生多次时,会合并为一个信号,其它都会丢失。

不可靠信号都是有预定值的,每个信号都有确定的用途及含义,并且每个信号都有各自缺省的动作。如按ctrl+c时,会产生SIGINT信号,对应的默认反应是进程终止。

可靠信号:

Linux在支持Unix不可靠信号的同时,还支持改进后的可靠信号。信号值位于SIGRTMINSIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。可靠信号类似于linux的软中断机制,实际上就是支持信号的排队,这样同一个信号同时发生多次时可以排队等待执行,不会丢失。

可靠信号没有被预定义,可以用于应用进程。可靠信号也都有缺省动作,默认反应都是结束进程。

 

2.2.  预定义与自定义

预定义信号:

不可靠信号同时也是预定义信号,它们都是有预定值的。每个信号都有确定的用途及含义,一般不会被用做其它用途,默认反应都是结束进程。每个信号根据其用途都有各自缺省的动作,如按ctrl+c时,会产生SIGINT信号。

自定义信号:

可靠信号同时也是自定义信号,它们没有被预定义。自定义可以用于应用进程,它们也都有缺省动作,默认反应都是结束进程。

3.   Hello world

程序:

#include

#include

#include

 

void new_op(int,siginfo_t*,void*);

 

int main(int argc,char**argv)

{

    struct sigaction act;

    int sig;

   

    sig = atoi(argv[1]);

    sigemptyset(&act.sa_mask);

    act.sa_flags = SA_SIGINFO;

    act.sa_sigaction = new_op;

    sigaction(sig, &act, NULL);

   

    while(1)

    {

        printf("hello world\n");

        sleep(2);

    }

}

void new_op(int signum,siginfo_t *info,void *myact)

{

    printf("***** receive signal %d ******\n", signum);

    sleep(5);

}

 

执行:

Ø  启动程序:“./a.out 38&  (注:&作用是程序在新进程中执行)

Ø  使用ps查看进程PID

Ø  执行“kill –s 38 pid  (注:pid为上面用ps得到的pid

Ø  会打印“***** receive signal %d ******\n

Ø  执行“kill –s 2 pid  (注:测试未启动的信号)

Ø  程序会退出

 

4.   信号基本函数

信号的安装函数有两个:signal()sigaction()signal()常用于非实时信号;sigaction()常用于实时信号,它有更多的选项设置,最重要的是可以为实时信号安装带参数的回调。

信号的发送函数有6个:kill()sigqueue()是最基本的信号发送函数,kill()用于发送不带参数的信号,sigqueue()用于发送带参数的信号;raise()函数是对kill的简单封装,只能给本进程发送信号。abort()是对SIGABRT信号的封装,可用kill()函数代替。alarm()专为SIGALRM设计,最大特点是可以实现信号的延迟发送,类似于一次性的定时器实际上就是:“定时器”+“信号”。setitmer()是更高级的定时器,它可以实现非周期定时器与周期定时器,还有更多的定时器类型。

4.1.  信号安装

4.1.1.    signal()

原型:

void (*signal(int signum, void (*handler))(int)))(int)

参数:

signum信号值

handler 处理函数,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)

返回值:

调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR

说明:

用于改变进程接收到特定信号后的行为,只能用于非实时信号(前32号)。

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler));

 

头文件:

#include

 

4.1.2.    sigaction()

原型:

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact))

参数:

signum信号值,可以为除SIGKILLSIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)

act指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理

oldact第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldactNULL

返回值:

调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR

说明:

用于改变进程接收到特定信号后的行为,可用于所有的信号。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性

头文件:

#include

 

第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。

sigaction结构定义如下:

struct sigaction {

    union{

        __sighandler_t _sa_handler;

        void (*_sa_sigaction)(int, struct siginfo *, void *);

    }_u

    sigset_t sa_mask

    unsigned long sa_flags

    void (*sa_restorer)(void)  /* 已废弃,不应被使用 */

}

 

其中,sa_restorer,已过时,POSIX不支持它,不应再被使用。

1、联合数据结构中的两个元素_sa_handler以及*_sa_sigaction指定信号关联函数,即用户指定的信号处理函数。除了可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为SIG_IGN(忽略信号)。

2、由_sa_handler指定的处理函数只有一个参数,即信号值,所以信号不能传递除信号值之外的任何信息;由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个3参数信号处理函数。第一个参数为信号值,第三个参数没有使用(posix没有规范使用该参数的标准),第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值,参数所指向的结构如下:

siginfo_t {

    int      si_signo;  /* 信号值,对所有信号有意义*/

    int      si_errno;  /* errno值,对所有信号有意义*/

    int      si_code;   /* 信号产生的原因,对所有信号有意义*/

    union{  /* 联合数据结构,不同成员适应不同信号 */ 

        //确保分配足够大的存储空间

        int _pad[SI_PAD_SIZE];

        //SIGKILL有意义的结构

        struct{

            ...

        }...

        ...

        ...

        //SIGILL, SIGFPE, SIGSEGV, SIGBUS有意义的结构

        struct{

            ...

        }...

        ...

    }

}

 

注:为了更便于阅读,在说明问题时常把该结构表示为下面的形式。

siginfo_t结构中的联合数据成员确保该结构适应所有的信号,比如对于实时信号来说,则实际采用下面的结构形式:

typedef struct {

    int si_signo;

    int si_errno;

    int si_code;

    union sigval si_value;

} siginfo_t;

 

结构的第四个域同样为一个联合数据结构:

union sigval {

    int sival_int;

    void *sival_ptr;

}

 

采用联合数据结构,说明siginfo_t结构中的si_value要么持有一个4字节的整数值,要么持有一个指针,这就构成了与信号相关的数据。在信号的处理函数中,包含这样的信号相关数据指针,但没有规定具体如何对这些数据进行操作,操作方法应该由程序开发人员根据具体任务事先约定。

调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义的。

信号参数的传递过程可图示如下:

3sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位。

注:请注意sa_mask指定的信号阻塞的前提条件,是在由sigaction()安装信号的处理函数执行过程中由sa_mask指定的信号才被阻塞。

4sa_flags中包含了许多标志位,包括刚刚提到的SA_NODEFERSA_NOMASK标志位。另一个比较重要的标志位是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不应该为sa_handler指定信号处理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation fault)。

注:很多文献在阐述该标志位时都认为,如果设置了该标志位,就必须定义三参数信号处理函数。实际不是这样的,验证方法很简单:自己实现一个单一参数信号处理函数,并在程序中设置该标志位,可以察看程序的运行结果。实际上,可以把该标志位看成信号是否传递参数的开关,如果设置该位,则传递参数;否则,不传递参数。

 

4.2.  信号发送

4.2.1.    raise()

原型:

int raise(int signo)

参数:

signo即将发送的信号值。

返回值:

调用成功返回 0;否则,返回 -1

说明:

向进程本身发送信号

头文件:

#include

例程:

#include

#include

#include

 

void new_op(int,siginfo_t*,void*);

 

int main(int argc,char**argv)

{

    struct sigaction act;

    int sig;

   

    sig = atoi(argv[1]);

   

    sigemptyset(&act.sa_mask);

    act.sa_flags = SA_SIGINFO;

    act.sa_sigaction = new_op;

    sigaction(sig, &act, NULL);

   

    while(1)

    {

        printf("hello world\n");

        raise(sig);

        sleep(1);

    }

}

void new_op(int signum,siginfo_t *info,void *myact)

{

    printf("***** receive signal %d ******\n", signum);

}

 

4.2.2.    kill()

原型:

int kill(pid_t pid, int signo)

参数:

pid  进程pid

参数pid的值

信号的接收进程

pid>0

进程IDpid的进程

pid=0

同一个进程组的进程

pid<0

pid!=-1

进程组ID -pid的所有进程

pid=-1

除发送进程自身外,所有进程ID1的进程

signo即将发送的信号值。sinno是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。

返回值:

调用成功返回 0;否则,返回 -1

说明:

向指定进程发送信号,kill()最常用于pid>0时的信号发送。注:对于pid<0时的情况,对于哪些进程将接受信号,各种版本说法不一。

头文件:

#include

#include

例程:

#include

#include

#include

 

void new_op(int,siginfo_t*,void*);

 

void *Test01(void *p)

{

    struct sigaction act;

   

    sigemptyset(&act.sa_mask);

    act.sa_flags = SA_SIGINFO;

    act.sa_sigaction = new_op;

    sigaction((int)p, &act, NULL);

    printf("\nsigal = %d\n", (int)p);

   

    while(1)

    {

        printf("hello world: self = 0x%08x\n", getpid());

        sleep(1);

    }

    return NULL;

}

 

int main(int argc,char**argv)

{

    int sig;

    pthread_t tid;

    int ret;

 

    sig = atoi(argv[1]);

   

    ret = fork();

    if (0 == ret)

    {

        Test01((void*)sig);

    }

   

    sleep(5);

    printf("pid = 0x%08x, send sig: %d\n", ret, sig);

    ret = kill(ret, sig);

    printf("ret=%d\n", ret);

    sleep(3);

    printf("Bye\n");

}

void new_op(int signum,siginfo_t *info,void *myact)

{

    printf("***** receive signal %d ******\n", signum);

}

 

4.2.3.    sigqueue()

原型:

int sigqueue(pid_t pid, int signo, const union sigval val)

参数:

pid  进程pid

signo即将发送的信号值。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。

val   信号传递的参数,即通常所说的4字节值。

     typedef union sigval {

         int  sival_int;

         void *sival_ptr;

     }sigval_t;

返回值:

成功返回 0;否则,返回 -1

说明:

针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。sigqueue()kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。

调用sigqueue后,sigaction中注册的回调函数的第二个参数siginfo中包含着sigval,位置为siginfo->_sifields._rt._sigval,加入是int性数据,可以使用宏siginfo-> si_int

注:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; sigqueue()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。

头文件:

#include

#include

例程:

#include

#include

#include

 

void new_op(int,siginfo_t*,void*);

 

void *Test01(void *p)

{

    struct sigaction act;

   

    sigemptyset(&act.sa_mask);

    act.sa_flags = SA_SIGINFO;

    act.sa_sigaction = new_op;

    sigaction((int)p, &act, NULL);

    printf("\nsigal = %d\n", (int)p);

   

    while(1)

    {

        printf("hello world: self = %d\n", getpid());

        sleep(1);

    }

    return NULL;

}

 

int main(int argc,char**argv)

{

    int sig;

    pthread_t tid;

    int ret;

    sigval_t sigval;

   

    sigval.sival_int = 0x12345678;

    sig = atoi(argv[1]);

   

    printf("\n");

    ret = fork();

    if (0 == ret)

    {

        Test01((void*)sig);

    }

   

    sleep(5);

    printf("pid = %d, send sig: %d\n", ret, sig);

    sigqueue(ret, sig, sigval);

    sleep(3);

    kill(ret, SIGINT);

    printf("Bye\n");

   

    return 0;

}

void new_op(int signum,siginfo_t *info,void *myact)

{

    printf("***** receive signal %d, info 0x%x ******\n", signum, info->si_int);

}

 

4.2.4.    alarm()

原型:

unsigned int alarm(unsigned int seconds)

参数:

seconds 指定时间。

返回值:

如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0

说明:

专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间,也就是非周期定时器。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。

头文件:

#include

例程:

#include

#include

#include

 

void new_op(int,siginfo_t*,void*);

 

int main(int argc,char**argv)

{

 

    if (SIG_ERR == signal(SIGALRM, new_op))

    {

        return 0;

    }

   

    alarm(5);

   

    while(1)

    {

        printf("hello world\n");

        sleep(1);

    }

   

    return 0;

}

void new_op(int signum,siginfo_t *info,void *myact)

{

    printf("***** receive signal %d ******\n", signum);

}

 

4.2.5.    setitmer()

原型:

int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue))

参数:

which定时器类型

value 定时时长,支持三种类型

ITIMER_REAL

ITIMER_VIRTUAL

ITIMER_PROF

ovalue旧的定时时长,一般添NULL

返回值:

调用成功返回 0;否则,返回 -1

说明:

setitimeralarm功能强大,它可以实现周期定时器与非周期定时器两种功能。

setitimer支持3种类型的定时器:

ITIMER_REAL 设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程;

ITIMER_VIRTUAL 设定程序执行时间;经过指定的时间后,内核将发送SIGVTALRM信号给本进程;

ITIMER_PROF 设定进程执行以及内核因本进程而消耗的时间和,经过指定的时间后,内核将发送ITIMER_VIRTUAL信号给本进程;

第二个参数value是有两个成员变量的结构体,定义如下。interval设置了定时器的运行周期,value定义了设置后第一次运行的时间。比如需要在2s后启动一个10s周期的打印,interval10value2interval0表示为非周期定时器。value0表示取消定时器。

struct itimerval {

    struct timeval it_interval; /* timer interval*/

    struct timeval it_value;    /* current value */

};

struct timeval {

    long tv_sec;                /* seconds */

    long tv_usec;               /* microseconds */

}

 

头文件:

#include

例程:

#include

#include

#include

#include

 

void new_op(int,siginfo_t*,void*);

 

int main(int argc,char**argv)

{

 

    struct itimerval tt;

 

    /* 设定定时间隔 */

    tt.it_value.tv_sec = 5;

    tt.it_value.tv_usec = 0;

    tt.it_interval.tv_sec = 1;

    tt.it_interval.tv_usec = 0;

 

    setitimer(ITIMER_REAL, &tt, NULL);

 

    if (SIG_ERR == signal(SIGALRM, new_op))

    {

        return 0;

    }

   

    while(1)

    {

        printf("hello world\n");

        sleep(1);

    }

   

    return 0;

}

void new_op(int signum,siginfo_t *info,void *myact)

{

printf("***** receive signal %d ******\n", signum);

}

 

4.2.6.    abort()

原型:

void abort(void)

参数:

signo即将发送的信号值。

返回值:

说明:

向进程发送SIGABRT信号,默认情况下进程会异常退出,可定义自己的信号处理函数。即使添加了自己的处理函数,进程仍然会在执行完回调后终止。

头文件:

#include

例程:

#include

#include

#include

 

void new_op(int,siginfo_t*,void*);

 

int main(int argc,char**argv)

{

 

    if (SIG_ERR == signal(SIGABRT, new_op))

    {

        return 0;

    }

   

    abort();

   

    while(1)

    {

        printf("hello world\n");

        sleep(1);

    }

   

    return 0;

}

void new_op(int signum,siginfo_t *info,void *myact)

{

    printf("***** receive signal %d ******\n", signum);

}

 

5.   附录A 信号列表

1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。

 

另外注意:

多个实时信号的响应顺序是有保证的。

如果一个进程同时有实时信号和标准信号在pending,则优先响应标准信号。

大家用的一般都是非时实信号,下边是信号列表.

 

Linux信号的种类有60多种。可以用kill -l命令查看所有的信号,每个信号的含义如下:

1)SIGHUP:当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程

2SIGINT:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。

3SIGQUIT:当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作为终止进程。

4SIGILLCPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件

5SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。

6)SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。

7SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。

8SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。

9SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。

10SIGUSE1:用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。

11SIGSEGV:指示进程进行了无数内存访问。默认动作为终止进程并产生core文件。

12SIGUSR2:这是另外一个用户自定义信号 ,程序员可以在程序中定义并使用该信号。默认动作为终止进程。1

13SIGPIPEBroken pipe向一个没有读端的管道写数据。默认动作为终止进程。

14)SIGALRM:定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。

15SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。

16SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号

17SIGCONT:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为终止进程。

18SIGTTIN:停止进程的运行,但该信号可以被处理和忽略。按下<ctrl+z>组合键发出灾个信号。默认动作为暂停进程。

19SIGTSTP:停止进程的运行,可该信号可以被处理可忽略。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。

21SIGTTOU:信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。

22SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。默认动作为忽略该信号

23SIGXFSZ:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止进程。

24SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。

25SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。

26SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。

27SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号

28SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略。

29SIGPWR:关机。默认动作为终止进程。

30SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。

31SIGRTMIN~(64SIGRTMAXLINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程

 

在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:SIGKILL, SIGSTOP

不能恢复至默认动作的信号有:SIGILL, SIGTRAP

默认会导致进程流产的信号有:SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGIOT, SIGQUIT, SIGSEGV, SIGTRAP, SIGXCPU, SIGXFSZ

默认会导致进程退出的信号有:SIGALRM, SIGHUP, SIGINT, SIGKILL, SIGPIPE, SIGPOLL, SIGPROF, SIGSYS, SIGTERM, SIGUSR1, SIGUSR2, SIGVTALRM

默认会导致进程停止的信号有:SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU

默认进程忽略的信号有:SIGCHLD, SIGPWR, SIGURG, SIGWINCH

 

此外,SIGIOSVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。

 

6.   参考文档

Linux环境进程间通信(二): 信号    郑彦兴 (mlinux@163.com)国防科大

阅读(335) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~