免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1935 | 回复: 0
打印 上一主题 下一主题

Linux环境进程间通信 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-08-21 09:51 |只看该作者 |倒序浏览

                       
system V队列
                       
                        Linux环境进程间通信(三):消息队列
作者:
郑彦兴
    阅读人次:2704    文章来源:
developerWorks
    发布时间:2007-8-29   
网友评论([color="#cc0000"]0)条

本系列文章中的前两部分,我们探讨[color="#003399"]管道及[color="#003399"]信号两种通信机制,本文将深入第三部分,介绍系统 V 消息队列及其相应 API。
消息队列(也叫做报文队列)能够克服早期unix通信机制的一些缺点。作为早期unix通信机制之一的信号能够传送的信息量有限,后来虽然
POSIX
1003.1b在信号的实时性方面作了拓广,使得信号在传递信息量方面有了相当程度的改进,但是信号这种通信方式更像"即时"的通信方式,它要求接受信号
的进程在某个时间范围内对信号做出反应,因此该信号最多在接受信号进程的生命周期内才有意义,信号所传递的信息是接近于随进程持续的概念
(process-persistent),见[color="#003399"]附录 1;管道及有名管道及有名管道则是典型的随进程持续IPC,并且,只能传送无格式的字节流无疑会给应用程序开发带来不便,另外,它的缓冲区大小也受到限制。
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的(参见[color="#003399"]附录 1)。
目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。考虑到程序的可移植性,新开发的应用程序应尽量使用POSIX消息队列。
在本系列专题的序(深刻理解Linux进程间通信(IPC))中,提到对于消息队列、信号灯、以及共享内存区来说,有两个实现版本:POSIX的以
及系统V的。Linux内核(内核2.4.18)支持POSIX信号灯、POSIX共享内存区以及POSIX消息队列,但对于主流Linux发行版本之一
redhad8.0(内核2.4.18),还没有提供对POSIX进程间通信API的支持,不过应该只是时间上的事。
因此,本文将主要介绍系统V消息队列及其相应API。在没有声明的情况下,以下讨论中指的都是系统V消息队列。
[color="#003399"]一、消息队列基本概念
  • 系统V消息队列是随内核持续的,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。因此系统中记录消息队列的数据结构(struct ipc_ids msg_ids)位于内核中,系统中的所有消息队列都可以在结构msg_ids中找到访问入口。
       
  • 消息队列就是一个消息的链表。每个消息队列都有一个队列头,用结构struct msg_queue来描述(参见[color="#003399"]附录 2)。队列头中包含了该消息队列的大量信息,包括消息队列键值、用户ID、组ID、消息队列中消息数目等等,甚至记录了最近对消息队列读写进程的ID。读者可以访问这些信息,也可以设置其中的某些信息。
       
  • 下图说明了内核与消息队列是怎样建立起联系的:
    其中:struct ipc_ids msg_ids是内核中记录消息队列的全局数据结构;struct msg_queue是每个消息队列的队列头。
       

    从上图可以看出,全局数据结构 struct ipc_ids msg_ids 可以访问到每个消息队列头的第一个成员:struct
    kern_ipc_perm;而每个struct
    kern_ipc_perm能够与具体的消息队列对应起来是因为在该结构中,有一个key_t类型成员key,而key则唯一确定一个消息队列。
    kern_ipc_perm结构如下:
       
            
                
                struct kern_ipc_perm{   //内核中记录消息队列的全局数据结构msg_ids能够访问到该结构;
                key_t   key;    //该键值则唯一对应一个消息队列
                uid_t   uid;
                gid_t   gid;
    uid_t   cuid;
    gid_t   cgid;
    mode_t  mode;
    unsigned long seq;
    }
                
            
       
    [color="#003399"]二、操作消息队列
    对消息队列的操作无非有下面三种类型:
    1、 打开或创建消息队列
    消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,只需提供该消息队列的键值即可;
    注:消息队列描述字是由在系统范围内唯一的键值生成的,而键值可以看作对应系统内的一条路经。
    2、 读写操作
    消息读写操作非常简单,对开发人员来说,每个消息都类似如下的数据结构:
       
            
                
                struct msgbuf{
    long mtype;
    char mtext[1];
    };
                
            
       
    mtype成员代表消息类型,从消息队列中读取消息的一个重要依据就是消息的类型;mtext是消息内容,当然长度不一定为1。因此,对于发送消息
    来说,首先预置一个msgbuf缓冲区并写入消息类型和内容,调用相应的发送函数即可;对读取消息来说,首先分配这样一个msgbuf缓冲区,然后把消息
    读入该缓冲区即可。
    3、 获得或设置消息队列属性:
    消息队列的信息基本上都保存在消息队列头中,因此,可以分配一个类似于消息队列头的结构(struct msqid_ds,见[color="#003399"]附录 2),来返回消息队列的属性;同样可以设置该数据结构。

    消息队列API
    1、文件名到键值
       
            
                
                #include
    #include
    key_t ftok (char*pathname, char proj);
                
            
       
    它返回与路径pathname相对应的一个键值。该函数不直接对消息队列操作,但在调用ipc(MSGGET,…)或msgget()来获得消息队列描述字前,往往要调用该函数。典型的调用代码是:
       
            
                
                   key=ftok(path_ptr, 'a');
        ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0);
        …
                
            
       
    2、linux为操作系统V进程间通信的三种方式(消息队列、信号灯、共享内存区)提供了一个统一的用户界面:
    int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);
    第一个参数指明对IPC对象的操作方式,对消息队列而言共有四种操作:MSGSND、MSGRCV、MSGGET以及MSGCTL,分别代表向消息
    队列发送消息、从消息队列读取消息、打开或创建消息队列、控制消息队列;first参数代表唯一的IPC对象;下面将介绍四种操作。
  • int ipc(MSGGET, int first, int second, int third, void *ptr, long fifth);
    与该操作对应的系统V调用为:int msgget( (key_t)first,second)。
       
  • int ipc(MSGCTL, int first, int second, int third, void *ptr, long fifth)
    与该操作对应的系统V调用为:int msgctl( first,second, (struct msqid_ds*) ptr)。
       
  • int ipc(MSGSND, int first, int second, int third, void *ptr, long fifth);
    与该操作对应的系统V调用为:int msgsnd( first, (struct msgbuf*)ptr, second, third)。
       
  • int ipc(MSGRCV, int first, int second, int third, void *ptr, long fifth);
    与该操作对应的系统V调用为:int msgrcv( first,(struct msgbuf*)ptr, second, fifth,third),
    注:本人不主张采用系统调用ipc(),而更倾向于采用系统V或者POSIX进程间通信API。原因如下:
  • 虽然该系统调用提供了统一的用户界面,但正是由于这个特性,它的参数几乎不能给出特定的实际意义(如以first、second来命名参数),在一定程度上造成开发不便。
       
  • 正如ipc手册所说的:ipc()是linux所特有的,编写程序时应注意程序的移植性问题;
       
  • 该系统调用的实现不过是把系统V IPC函数进行了封装,没有任何效率上的优势;
       
  • 系统V在IPC方面的API数量不多,形式也较简洁。
    3.系统V消息队列API
    系统V消息队列API共有四个,使用时需要包括几个头文件:
       
            
                
                #include
    #include
    #include
                
            
       
    1)int msgget(key_t key, int msgflg)
    参数key是一个键值,由ftok获得;msgflg参数是一些标志位。该调用返回与健值key相对应的消息队列描述字。
    在以下两种情况下,该调用将创建一个新的消息队列:
  • 如果没有消息队列与健值key相对应,并且msgflg中包含了IPC_CREAT标志位;
       
  • key参数为IPC_PRIVATE;
    参数msgflg可以为以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果。
    调用返回:成功返回消息队列描述字,否则返回-1。
    注:参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只意味着即将创建新的消息队列。
    2)int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);
    该系统调用从msgid代表的消息队列中读取一个消息,并把消息存储在msgp指向的msgbuf结构中。
    msqid为消息队列描述字;消息返回后存储在msgp指向的地址,msgsz指定msgbuf的mtext成员的长度(即消息内容的长度),msgtyp为请求读取的消息类型;读消息标志msgflg可以为以下几个常值的或:
  • IPC_NOWAIT 如果没有满足条件的消息,调用立即返回,此时,errno=ENOMSG
       
  • IPC_EXCEPT 与msgtyp>0配合使用,返回队列中第一个类型不为msgtyp的消息
       
  • IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的msgsz字节,则把该消息截断,截断部分将丢失。
    msgrcv手册中详细给出了消息类型取不同值时(>0;
  • 消息队列中有了满足条件的消息;
       
  • msqid代表的消息队列被删除;
       
  • 调用msgrcv()的进程被信号中断;
    调用返回:成功返回读出消息的实际字节数,否则返回-1。
    3)int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
    向msgid代表的消息队列发送一个消息,即将发送的消息存储在msgp指向的msgbuf结构中,消息的大小由msgze指定。
    对发送消息来说,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。造成msgsnd()等待的条件有两种:
  • 当前消息的大小与当前消息队列中的字节数之和超过了消息队列的总容量;
       
  • 当前消息队列的消息数(单位"个")不小于消息队列的总容量(单位"字节数"),此时,虽然消息队列中的消息数目很多,但基本上都只有一个字节。
    msgsnd()解除阻塞的条件有三个:
  • 不满足上述两个条件,即消息队列中有容纳该消息的空间;
       
  • msqid代表的消息队列被删除;
       
  • 调用msgsnd()的进程被信号中断;
    调用返回:成功返回0,否则返回-1。
    4)int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    该系统调用对由msqid标识的消息队列执行cmd操作,共有三种cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。
  • IPC_STAT:该命令用来获取消息队列信息,返回的信息存贮在buf指向的msqid结构中;
       
  • IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf指向的msqid结构中;可设置属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。
       
  • IPC_RMID:删除msqid标识的消息队列;
    调用返回:成功返回0,否则返回-1。
    [color="#003399"]三、消息队列的限制
    每个消息队列的容量(所能容纳的字节数)都有限制,该值因系统不同而不同。在后面的应用实例中,输出了redhat 8.0的限制,结果参见[color="#003399"]附录 3。
    另一个限制是每个消息队列所能容纳的最大消息数:在redhad 8.0中,该限制是受消息队列容量制约的:消息个数要小于消息队列的容量(字节数)。
    注:上述两个限制是针对每个消息队列而言的,系统对消息队列的限制还有系统范围内的最大消息队列个数,以及整个系统范围内的最大消息数。一般来说,实际开发过程中不会超过这个限制。
    [color="#003399"]四、消息队列应用实例
    消息队列应用相对较简单,下面实例基本上覆盖了对消息队列的所有操作,同时,程序输出结果有助于加深对前面所讲的某些规则及消息队列限制的理解。
       
            
                
                #include
    #include
    #include
    void msg_stat(int,struct msqid_ds );
    main()
    {
    int gflags,sflags,rflags;
    key_t key;
    int msgid;
    int reval;
    struct msgsbuf{
            int mtype;
            char mtext[1];
        }msg_sbuf;
    struct msgmbuf
        {
        int mtype;
        char mtext[10];
        }msg_rbuf;
    struct msqid_ds msg_ginfo,msg_sinfo;
    char* msgpath="/unix/msgqueue";
    key=ftok(msgpath,'a');
    gflags=IPC_CREAT|IPC_EXCL;
    msgid=msgget(key,gflags|00666);
    if(msgid==-1)
    {
        printf("msg create error\n");
        return;
    }
    //创建一个消息队列后,输出消息队列缺省属性
    msg_stat(msgid,msg_ginfo);
    sflags=IPC_NOWAIT;
    msg_sbuf.mtype=10;
    msg_sbuf.mtext[0]='a';
    reval=msgsnd(msgid,&msg_sbuf,sizeof(msg_sbuf.mtext),sflags);
    if(reval==-1)
    {
        printf("message send error\n");
    }
    //发送一个消息后,输出消息队列属性
    msg_stat(msgid,msg_ginfo);
    rflags=IPC_NOWAIT|MSG_NOERROR;
    reval=msgrcv(msgid,&msg_rbuf,4,10,rflags);
    if(reval==-1)
        printf("read msg error\n");
    else
        printf("read from msg queue %d bytes\n",reval);
    //从消息队列中读出消息后,输出消息队列属性
    msg_stat(msgid,msg_ginfo);
    msg_sinfo.msg_perm.uid=8;//just a try
    msg_sinfo.msg_perm.gid=8;//
    msg_sinfo.msg_qbytes=16388;
    //此处验证超级用户可以更改消息队列的缺省msg_qbytes
    //注意这里设置的值大于缺省值
    reval=msgctl(msgid,IPC_SET,&msg_sinfo);
    if(reval==-1)
    {
        printf("msg set info error\n");
        return;
    }
    msg_stat(msgid,msg_ginfo);
    //验证设置消息队列属性
    reval=msgctl(msgid,IPC_RMID,NULL);//删除消息队列
    if(reval==-1)
    {
        printf("unlink msg queue error\n");
        return;
    }
    }
    void msg_stat(int msgid,struct msqid_ds msg_info)
    {
    int reval;
    sleep(1);//只是为了后面输出时间的方便
    reval=msgctl(msgid,IPC_STAT,&msg_info);
    if(reval==-1)
    {
        printf("get msg info error\n");
        return;
    }
    printf("\n");
    printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes);
    printf("number of messages in queue is %d\n",msg_info.msg_qnum);
    printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);
    //每个消息队列的容量(字节数)都有限制MSGMNB,值的大小因系统而异。在创建新的消息队列时,//msg_qbytes的缺省值就是MSGMNB
    printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);
    printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);
    printf("last msgsnd time is %s", ctime(&(msg_info.msg_stime)));
    printf("last msgrcv time is %s", ctime(&(msg_info.msg_rtime)));
    printf("last change time is %s", ctime(&(msg_info.msg_ctime)));
    printf("msg uid is %d\n",msg_info.msg_perm.uid);
    printf("msg gid is %d\n",msg_info.msg_perm.gid);
    }
                
            
       
    程序输出结果见[color="#003399"]附录 3。
    [color="#003399"]小结:
    消息队列
    与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使
    用。这两点是管道以及有名管道所不能比的。同样,消息队列可以在几个进程间复用,而不管这几个进程是否具有亲缘关系,这一点与有名管道很相似;但消息队列
    是随内核持续的,与有名管道(随进程持续)相比,生命力更强,应用空间更大。
    [color="#003399"]附录 1:在参考文献[1]中,给出了IPC随进程持续、随内核持续以及随文件系统持续的定义:
  • 随进程持续:IPC一直存在到打开IPC对象的最后一个进程关闭该对象为止。如管道和有名管道;
       
  • 随内核持续:IPC一直持续到内核重新自举或者显示删除该对象为止。如消息队列、信号灯以及共享内存等;
       
  • 随文件系统持续:IPC一直持续到显示删除该对象为止。
    [color="#003399"]附录 2:
    结构msg_queue用来描述消息队列头,存在于系统空间:
       
            
                
                struct msg_queue {
        struct kern_ipc_perm q_perm;
        time_t q_stime;         /* last msgsnd time */
        time_t q_rtime;         /* last msgrcv time */
        time_t q_ctime;         /* last change time */
        unsigned long q_cbytes;     /* current number of bytes on queue */
        unsigned long q_qnum;       /* number of messages in queue */
        unsigned long q_qbytes;     /* max number of bytes on queue */
        pid_t q_lspid;          /* pid of last msgsnd */
        pid_t q_lrpid;          /* last receive pid */
        struct list_head q_messages;
        struct list_head q_receivers;
        struct list_head q_senders;
    };
                
            
       
    结构msqid_ds用来设置或返回消息队列的信息,存在于用户空间;
       
            
                
                struct msqid_ds {
        struct ipc_perm msg_perm;
        struct msg *msg_first;      /* first message on queue,unused  */
        struct msg *msg_last;       /* last message in queue,unused */
        __kernel_time_t msg_stime;  /* last msgsnd time */
        __kernel_time_t msg_rtime;  /* last msgrcv time */
        __kernel_time_t msg_ctime;  /* last change time */
        unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
        unsigned long  msg_lqbytes; /* ditto */
        unsigned short msg_cbytes;  /* current number of bytes on queue */
        unsigned short msg_qnum;    /* number of messages in queue */
        unsigned short msg_qbytes;  /* max number of bytes on queue */
        __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
        __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
    };
                
            
       
    //可以看出上述两个结构很相似。
    [color="#003399"]附录 3:消息队列实例输出结果:
       
            
                
                current number of bytes on queue is 0
    number of messages in queue is 0
    max number of bytes on queue is 16384
    pid of last msgsnd is 0
    pid of last msgrcv is 0
    last msgsnd time is Thu Jan  1 08:00:00 1970
    last msgrcv time is Thu Jan  1 08:00:00 1970
    last change time is Sun Dec 29 18:28:20 2002
    msg uid is 0
    msg gid is 0
    //上面刚刚创建一个新消息队列时的输出
    current number of bytes on queue is 1
    number of messages in queue is 1
    max number of bytes on queue is 16384
    pid of last msgsnd is 2510
    pid of last msgrcv is 0
    last msgsnd time is Sun Dec 29 18:28:21 2002
    last msgrcv time is Thu Jan  1 08:00:00 1970
    last change time is Sun Dec 29 18:28:20 2002
    msg uid is 0
    msg gid is 0
    read from msg queue 1 bytes
    //实际读出的字节数
    current number of bytes on queue is 0
    number of messages in queue is 0
    max number of bytes on queue is 16384   //每个消息队列最大容量(字节数)
    pid of last msgsnd is 2510
    pid of last msgrcv is 2510
    last msgsnd time is Sun Dec 29 18:28:21 2002
    last msgrcv time is Sun Dec 29 18:28:22 2002
    last change time is Sun Dec 29 18:28:20 2002
    msg uid is 0
    msg gid is 0
    current number of bytes on queue is 0
    number of messages in queue is 0
    max number of bytes on queue is 16388   //可看出超级用户可修改消息队列最大容量
    pid of last msgsnd is 2510
    pid of last msgrcv is 2510  //对操作消息队列进程的跟踪
    last msgsnd time is Sun Dec 29 18:28:21 2002
    last msgrcv time is Sun Dec 29 18:28:22 2002
    last change time is Sun Dec 29 18:28:23 2002    //msgctl()调用对msg_ctime有影响
    msg uid is 8
    msg gid is 8
                
            
       
    [color="#003399"]参考文献:
    • UNIX网络编程第二卷:进程间通信,作者:W.Richard Stevens,译者:杨继张,清华大学出版社。对POSIX以及系统V消息队列都有阐述,对Linux环境下的程序开发有极大的启发意义。
         
    • linux内核源代码情景分析(上),毛德操、胡希明著,浙江大学出版社,给出了系统V消息队列相关的源代码分析。
         
    • [color="#003399"]http://www.fanqiang.com/a4/b2/20010508/113315.html,主要阐述linux下对文件的操作,详细介绍了对文件的存取权限位,对IPC对象的存取权限同样具有很好的借鉴意义。
         
    • msgget、msgsnd、msgrcv、msgctl手册

                           
                                           
                            posted @
    2008-10-22 09:31
    茶 阅读(36) |
    评论 (0)
    |
    编辑

    收藏
                           
                   
           
           
                   
                              
           
    2008年10月20日
           
                   
                           
    C时间函数集
                           
                           
    http://blog.programfan.com/trackback.asp?id=6922
    C语言的标准库函数包括一系列日期和时间处理函数,它们都在头文件中说明。下面列出了这些函数。在头文件中定义了三种类型:time_t,struct tm和clock_t。
        在中说明的C语言时间函数                                                      
       time_t time(time_t *timer);
       double difftime(time_t time1,time_t time2);
       struct tm *gmtime(const time_t *timer);
       struct tm *localtime(const time_t *timer);
       char *asctime(const struct tm *timeptr);
       char *ctime(const time_t *timer);
       size_t strftime(char *s,size_t maxsize,const char *format,const struct tm *timeptr);
       time_t mktime(struct tm *timeptr);
       clock_t clock(void);

        下面是我从网上收集到的时间函数集

       
            
                
                
                
                
    asctime(将时间和日期以字符串格式表示)
            
            
                相关函数
                time,ctime,gmtime,localtime
            
            
                表头文件
                #include
            
            
                定义函数
                char * asctime(const struct tm * timeptr);
            
            
                函数说明
                asctime()将参数timeptr所指的tm结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果以字符串形态返回。此函数已经由时区转换成当地时间,字符串格式为:"Wed Jun 30 21:49:08 1993\n"
            
            
                返回值
                若再调用相关的时间日期函数,此字符串可能会被破坏。此函数与ctime不同处在于传入的参数是不同的结构。
            
            
                附加说明
                返回一字符串表示目前当地的时间日期。
            
            
                范例
                #include
    main()
    {
    time_t timep;
    time (&timep);
    printf("%s",asctime(gmtime(&timep)));
    }
            
            
                执行
                Sat Oct 28 02:10:06 2000
            
            
                 
                
                
            
            
                
                
    ctime(将时间和日期以字符串格式表示)
            
            
                相关函数
                time,asctime,gmtime,localtime
            
            
                表头文件
                #include
            
            
                定义函数
                char *ctime(const time_t *timep);
            
            
                函数说明
                ctime()将参数timep所指的time_t结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果
                以字符串形态返回。此函数已经由时区转换成当地时间,字符串格式为"Wed Jun 30 21 :49 :08
                1993\n"。若再调用相关的时间日期函数,此字符串可能会被破坏。
            
            
                返回值
                返回一字符串表示目前当地的时间日期。
            
            
                范例
                #include
    main()
    {
    time_t timep;
    time (&timep);
    printf("%s",ctime(&timep));
    }
            
            
                执行
                Sat Oct 28 10 : 12 : 05 2000
            
            
                 
                
                
            
            
                
                
    gettimeofday(取得目前的时间)
            
            
                相关函数
                time,ctime,ftime,settimeofday
            
            
                表头文件
                #include
    #include
            
            
                定义函数
                int gettimeofday ( struct timeval * tv , struct timezone * tz )
            
            
                函数说明
                gettimeofday()会把目前的时间有tv所指的结构返回,当地时区的信息则放到tz所指的结构中。
    timeval结构定义为:
    struct timeval{
    long tv_sec; /*秒*/
    long tv_usec; /*微秒*/
    };
    timezone 结构定义为:
    struct timezone{
    int tz_minuteswest; /*和Greenwich 时间差了多少分钟*/
    int tz_dsttime; /*日光节约时间的状态*/
    };
    上述两个结构都定义在/usr/include/sys/time.h。tz_dsttime 所代表的状态如下
    DST_NONE /*不使用*/
    DST_USA /*美国*/
    DST_AUST /*澳洲*/
    DST_WET /*西欧*/
    DST_MET /*中欧*/
    DST_EET /*东欧*/
    DST_CAN /*加拿大*/
    DST_GB /*大不列颠*/
    DST_RUM /*罗马尼亚*/
    DST_TUR /*土耳其*/
    DST_AUSTALT /*澳洲(1986年以后)*/
            
            
                返回值
                成功则返回0,失败返回-1,错误代码存于errno。附加说明EFAULT指针tv和tz所指的内存空间超出存取权限。
            
            
                范例
                #include
    #include
    main(){
    struct timeval tv;
    struct timezone tz;
    gettimeofday (&tv , &tz);
    printf("tv_sec; %d\n", tv,.tv_sec) ;
    printf("tv_usec; %d\n",tv.tv_usec);
    printf("tz_minuteswest; %d\n", tz.tz_minuteswest);
    printf("tz_dsttime, %d\n",tz.tz_dsttime);
    }
            
            
                执行
                tv_sec: 974857339
    tv_usec:136996
    tz_minuteswest:-540
    tz_dsttime:0
            
            
                 
                
                
            
            
                
                
    gmtime(取得目前时间和日期)
            
            
                相关函数
                time,asctime,ctime,localtime
            
            
                表头文件
                #include
            
            
                定义函数
                struct tm*gmtime(const time_t*timep);
            
            
                函数说明
                gmtime()将参数timep 所指的time_t 结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果由结构tm返回。
    结构tm的定义为
    struct tm
    {
    int tm_sec;
    int tm_min;
    int tm_hour;
    int tm_mday;
    int tm_mon;
    int tm_year;
    int tm_wday;
    int tm_yday;
    int tm_isdst;
    };
    int tm_sec 代表目前秒数,正常范围为0-59,但允许至61秒
    int tm_min 代表目前分数,范围0-59
    int tm_hour 从午夜算起的时数,范围为0-23
    int tm_mday 目前月份的日数,范围01-31
    int tm_mon 代表目前月份,从一月算起,范围从0-11
    int tm_year 从1900 年算起至今的年数
    int tm_wday 一星期的日数,从星期一算起,范围为0-6
    int tm_yday 从今年1月1日算起至今的天数,范围为0-365
    int tm_isdst 日光节约时间的旗标
    此函数返回的时间日期未经时区转换,而是UTC时间。
            
            
                返回值
                返回结构tm代表目前UTC 时间
            
            
                范例
                #include
    main(){
    char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
    time_t timep;
    struct tm *p;
    time(&timep);
    p=gmtime(&timep);
    printf("%d%d%d",(1900+p->tm_year), (1+p->tm_mon),p->tm_mday);
    printf("%s%d;%d;%d\n", wday[p->tm_wday], p->tm_hour, p->tm_min, p->tm_sec);
    }
            
            
                执行
                2000/10/28 Sat 8:15:38
            
            
                 
                
                
            
            
                
                
    localtime(取得当地目前时间和日期)
            
            
                相关函数
                time, asctime, ctime, gmtime
            
            
                表头文件
                #include
            
            
                定义函数
                struct tm *localtime(const time_t * timep);
            
            
                函数说明
                localtime()将参数timep所指的time_t结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果由结构tm返回。结构tm的定义请参考gmtime()。此函数返回的时间日期已经转换成当地时区。
            
            
                返回值
                返回结构tm代表目前的当地时间。
            
            
                范例
                #include
    main(){
    char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
    time_t timep;
    struct tm *p;
    time(&timep);
    p=localtime(&timep); /*取得当地时间*/
    printf ("%d%d%d ", (1900+p->tm_year),( l+p->tm_mon), p->tm_mday);
    printf("%s%d:%d:%d\n", wday[p->tm_wday],p->tm_hour, p->tm_min, p->tm_sec);
    }
            
            
                执行
                2000/10/28 Sat 11:12:22
            
            
                 
                
                
            
            
                
                
    mktime(将时间结构数据转换成经过的秒数)
            
            
                相关函数
                time,asctime,gmtime,localtime
            
            
                表头文件
                #include
            
            
                定义函数
                time_t mktime(strcut tm * timeptr);
            
            
                函数说明
                mktime()用来将参数timeptr所指的tm结构数据转换成从公元1970年1月1日0时0分0 秒算起至今的UTC时间所经过的秒数。
            
            
                返回值
                返回经过的秒数。
            
            
                范例
                /* 用time()取得时间(秒数),利用localtime()
    转换成struct tm 再利用mktine()将struct tm转换成原来的秒数*/
    #include
    main()
    {
    time_t timep;
    strcut tm *p;
    time(&timep);
    printf("time() : %d \n",timep);
    p=localtime(&timep);
    timep = mktime(p);
    printf("time()->localtime()->mktime():%d\n",timep);
    }
            
            
                执行
                time():974943297
    time()->localtime()->mktime():974943297
            
            
                 
                
                
            
            
                
                
    settimeofday(设置目前时间)
            
            
                相关函数
                time,ctime,ftime,gettimeofday
            
            
                表头文件
                #include
    #include
            
            
                定义函数
                int settimeofday ( const struct timeval *tv,const struct timezone *tz);
            
            
                函数说明
                settimeofday()会把目前时间设成由tv所指的结构信息,当地时区信息则设成tz所指的结构。详细的说明请参考gettimeofday()。注意,只有root权限才能使用此函数修改时间。
            
            
                返回值
                成功则返回0,失败返回-1,错误代码存于errno。
            
            
                错误代码
                EPERM 并非由root权限调用settimeofday(),权限不够。
    EINVAL 时区或某个数据是不正确的,无法正确设置时间。
            
            
                 
                
                
            
            
                
                
    time(取得目前的时间)
            
            
                相关函数
                ctime,ftime,gettimeofday
            
            
                表头文件
                #include
            
            
                定义函数
                time_t time(time_t *t);
            
            
                函数说明
                此函数会返回从公元1970年1月1日的UTC时间从0时0分0秒算起到现在所经过的秒数。如果t 并非空指针的话,此函数也会将返回值存到t指针所指的内存。
            
            
                返回值
                成功则返回秒数,失败则返回((time_t)-1)值,错误原因存于errno中。
            
            
                范例
                #include
    mian()
    {
    int seconds= time((time_t*)NULL);
    printf("%d\n",seconds);
    }
            
       

                           
                                           
                            posted @
    2008-10-20 18:31
    茶 阅读(116) |
    评论 (0)
    |
    编辑

    收藏
                           
                   
           
           
                   
                              
           
    2008年10月14日
           
                   
                           
    TinyXML中文文档
                           
                            http://www.hansencode.cn/2007/06/tinyxml-chinese-doc.html
    TinyXML是一个简单小巧,可以很容易集成到其它程序中的C++ XML解析器。
    它能做些什么
    简单地说,TinyXML解析一个XML文档并由此生成一个可读可修改可保存的文档对象模型(DOM)。
    XML的意思是“可扩展标记语言“(eXtensible Markup
    Language)。它允许你创建你自己的文档标记。在为浏览器标记文档方面HTML做得很好,然而XML允许你定义任何文档标记,比如可以为一个组织者
    应用程序定义一个描述“to do”列表的文档。
    XML拥有一个结构化并且方便的格式,所有为存储应用程序数据而创建的随机文件格式都可以用XML代替,而这一切只需要一个解析器。
    最全面正确的说明可以在
    http://www.w3.org/TR/2004/REC-xml-20040204/
    找到,但坦白地说,它很晦涩难懂。事实上我喜欢
    http://skew.org/xml/tutorial
    上关于XML的介绍。
    有不同的方法可以访问和与XML数据进行交互。TinyXML使用文档对象模型(DOM),这意味着XML数据被解析成一个可被浏览和操作的C++
    对象,然后它可以被写到磁盘或者另一个输出流中。你也可以把C++对象构造成一个XML文档然后把它写到磁盘或者另一个输出流中。
    TinyXML被设计得容易快速上手。它只有两个头文件和四个cpp文件。只需要把它们简单地加到你的项目中就行了。有一个例子文件——xmltest.cpp来引导你该怎么做。
    TinyXML以Zlib许可来发布,所以你可以在开源或者商业软件中使用它。许可证更具体的描述在每个源代码文件的顶部可以找到。
    TinyXML在保证正确和恰当的XML输出的基础上尝试成为一个灵活的解析器。TinyXML可以在任何合理的C++适用系统上编译。它不依赖于
    异常或者运行时类型信息,有没有STL支持都可以编译。TinyXML完全支持UTF-8编码和前64k个字符实体(译注:如果你不明
    白这句译文,可能你需要了解一下Unicode编码)。
    它无法做些什么
    TinyXML不解析不使用DTDs(文档类型定义)或者XSLs(可扩展样式表语言)。有其它解析器(到www.sourceforge.org
    搜索一下XML)具有更加全面的特性,但它们也就更大,需要花更长的时间来建立你的项目,有更陡的学习曲线,而且经常有一个更严格的许可协议。如果你是用
    于浏览器或者有更复杂的XML需要,那么TinyXML不适合你。
    下面的DTD语法在TinyXML里是不做解析的:

    ]>
    因为TinyXML把它看成是一个带着非法嵌入!ELEMENT结点的!DOCTYPE结点。或许这在将来会得到支持。
    指南
    有耐性些,这是一份能很好地指导你怎么开始的指南,它(非常短小精悍)值得你花时间完整地读上一遍。

    代码状况
    TinyXML是成熟且经过测试的代码,非常健壮。如果你发现了漏洞,请提交漏洞报告到sourcefore网站上 (www.sourceforge.net/projects/tinyxml)。 我们会尽快修正。
    有些地方可以让你得到提高,如果你对TinyXML的工作感兴趣的话可以上sourceforge查找一下。
    相关项目
    你也许会觉得TinyXML很有用!(简介由项目提供)

    特性
    使用STL
    TinyXML可以被编译成使用或不使用STL。如果使用STL,TinyXML会使用std::string类,而且完全支持
    std::istream,std::ostream,operator>。许多API方法都有
    ‘const char*’和’const std::string&’两个版本。
    如果被编译成不使用STL,则任何STL都不会被包含。所有string类都由TinyXML它自己实现。所有API方法都只提供’const char*’传入参数。
    使用运行时定义:
    TIXML_USE_STL
    来编译成不同的版本。这可以作为参数传给编译器或者在“tinyxml.h”文件的第一行进行设置。
    注意:如果在Linux上编译测试代码,设置环境变量TINYXML_USE_STL=YES/NO可以控制STL的编译。而在Windows上,
    项目文件提供了STL和非STL两种目标文件。在你的项目中,在tinyxml.h的第一行添加"#define
    TIXML_USE_STL"应该是最简单的。
    UTF-8
    TinyXML支持UTF-8,所以可以处理任何语言的XML文件,而且TinyXML也支持“legacy模式”——一种在支持UTF-8之前使用的编码方式,可能最好的解释是“扩展的ascii”。
    正常情况下,TinyXML会检测出正确的编码并使用它,然而,通过设置头文件中的TIXML_DEFAULT_ENCODING值,TinyXML可以被强制成总是使用某一种编码。
    除非以下情况发生,否则TinyXML会默认使用Legacy模式:
  • 如果文件或者数据流以非标准但普遍的"UTF-8引导字节" (0xef 0xbb 0xbf)开始,TinyXML会以UTF-8的方式来读取它。
  • 如果包含有encoding="UTF-8"的声明被读取,那么TinyXML会以UTF-8的方式来读取它。
  • 如果读取到没有指定编码方式的声明,那么TinyXML会以UTF-8的方式来读取它。
  • 如果包含有encoding=“其它编码”的声明被读取,那么TinyXML会以Legacy模式来读取它。在Legacy模式下,TinyXML会像以前那样工作,虽然已经不是很清楚这种模式是如何工作的了,但旧的内容还得保持能够运行。
  • 除了上面提到的情况,TinyXML会默认运行在Legacy模式下。
    如果编码设置错误或者检测到错误会发生什么事呢?TinyXML会尝试跳过这些看似不正确的编码,你可能会得到一些奇怪的结果或者乱码,你可以强制TinyXML使用正确的编码模式。
    通过使用LoadFile( TIXML_ENCODING_LEGACY )或者LoadFile( filename,
    TIXML_ENCODING_LEGACY ),
    你可以强制TinyXML使用Legacy模式。你也可以通过设置TIXML_DEFAULT_ENCODING =
    TIXML_ENCODING_LEGACY来强制一直使用Legacy模式。同样的,你也可以通过相同的方法来强制设置成
    TIXML_ENCODING_UTF8。
    对于使用英文XML的英语用户来说,UTF-8跟low-ASCII是一样的。你不需要知道UTF-8或者一点也不需要修改你的代码。你可以把UTF-8当作是ASCII的超集。
    UTF-8并不是一种双字节格式,但它是一种标准的Unicode编码!TinyXML当前不使用或者直接支持wchar,TCHAR,或者微软的
    _UNICODE。"Unicode"这个术语被普遍地认为指的是UTF-16(一种unicode的宽字节编码)是不适当的,这是混淆的来源。
    对于“high-ascii”语言来说——几乎所有非英语语言,只要XML被编码成UTF-8,
    TinyXML就能够处理。说起来可能有点微妙,比较旧的程序和操作系统趋向于使用“默认”或者“传统”的编码方式。许多应用程序(和几乎所有现在的应用
    程序)都能够输出UTF-8,但是那些比较旧或者难处理的(或者干脆不能使用的)系统还是只能以默认编码来输出文本。
    比如说,日本的系统传统上使用SHIFT-JIS编码,这种情况下TinyXML就无法读取了。但是一个好的文本编辑器可以导入SHIFT-JIS的文本然后保存成UTF-8编码格式的。
    Skew.org link
    上关于转换编码的话题做得很好。
    测试文件“utf8test.xml”包含了英文、西班牙文、俄文和简体中文(希望它们都能够被正确地转化)。“utf8test.gif”文件是
    从IE上截取的XML文件快照。请注意如果你的系统上没有正确的字体(简体中文或者俄文),那么即使你正确地解析了也看不到与GIF文件上一样的输出。同
    时要注意在一个西方编码的控制台上(至少我的Windows机器是这样),Print()或者printf()也无法正确地显示这个文件,这不关
    TinyXML的事——这只是操作系统的问题。TinyXML没有丢掉或者损坏数据,只是控制台无法显示UTF-8而已。
    实体
    TinyXML认得预定义的特殊“字符实体”,即:
    & &
    >
    " "
    ' ‘
    这些在XML文档读取时都会被辨认出来,并会被转化成等价的UTF-8字符。比如下面的XML文本:
    Far & Away
    从TiXmlText 对象查询出来时会变成"Far & Away"这样的值,而写回XML流/文件时会以“&”的方式写回。老版本的TinyXML“保留”了字符实体,而在新版本中它们会被转化成字符串。
    另外,所有字符都可以用它的Unicode编码数字来指定, " "和" "都表示不可分的空格字符。
    打印
    TinyXML有几种不同的方式来打印输出,当然它们各有各的优缺点。
    • Print( FILE* ):输出到一个标准C流中,包括所有的C文件和标准输出。
      • "相当漂亮的打印", 但你没法控制打印选项。
      • 输出数据直接写到FILE对象中,所以TinyXML代码没有内存负担。
      • 被Print()和SaveFile()调用。
    • operator
      • 与C++ iostreams集成在一起。
      • 在"network printing"模式下输出没有换行符,这对于网络传输和C++对象之间的XML交换有好处,但人很难阅读。
    • TiXmlPrinter:输出到一个std::string或者内存缓冲区中。
      • API还不是很简练。
      • 将来会增加打印选项。
      • 在将来的版本中可能有些细微的变化,因为它会被改进和扩展。


    设置了TIXML_USE_STL,TinyXML就能支持C++流(operator >)和C(FILE*)流。但它们之间有些差异你需要知道:
    C风格输出:
    • 基于FILE*
    • 用Print()和SaveFile()方法

    生成具有很多空格的格式化过的输出,这是为了尽可能让人看得明白。它们非常快,而且能够容忍XML文档中的格式错误。例如一个XML文档包含两个根元素和两个声明仍然能被打印出来。
    C风格输入:
    • 基于FILE*
    • 用Parse()和LoadFile()方法

    速度快,容错性好。当你不需要C++流时就可以使用它。
    C++风格输出:
    • 基于std::ostream
    • operator

    生成压缩过的输出,目的是为了便于网络传输而不是为了可读性。它可能有些慢(可能不会),这主要跟你系统上ostream类的实现有关。无法容忍格式错误的XML:此文档只能包含一个根元素。另外根级别的元素无法以流形式输出。
    C++风格输入:
    • 基于std::istream
    • operator>>

    从流中读取XML使其可用于网络传输。通过些小技巧,它知道当XML文档读取完毕时,流后面的就一定是其它数据了。TinyXML总假定当它读取到
    根结点后XML数据就结束了。换句话说,那些具有不止一个根元素的文档是无法被正确读取的。另外还要注意由于STL的实现和TinyXML的限
    制,operator>>会比Parse慢一些。
    空格
    对是保留还是压缩空格这一问题人们还没达成共识。举个例子,假设‘_’代表一个空格,对于"Hello____world",HTML和某些XML
    解析器会解释成"Hello_world",它们压缩掉了一些空格。而有些XML解析器却不会这样,它们会保留空格,于是就是
    “Hello____world”(记住_表示一个空格)。其它的还建议__Hello___world__应该变成Hello___world 。
    这是一个解决得不能让我满意的问题。TinyXML一开始就两种方式都支持。调用TiXmlBase::SetCondenseWhiteSpace( bool )来设置你想要的结果,默认是压缩掉多余的空格。
    如果想要改变默认行为,你应该在解析任何XML数据之前调用TiXmlBase::SetCondenseWhiteSpace( bool ) ,而且我不建议设置之后再去改动它。
    句柄
    想要健壮地读取一个XML文档,检查方法调用后的返回值是否为null是很重要的。一种安全的检错实现可能会产生像这样的代码:
    TiXmlElement* root = document.FirstChildElement( [color="#ff00ff"]"Document" );
    [color="#0000ff"]if ( root )
    {
        TiXmlElement* element = root->FirstChildElement( [color="#ff00ff"]"Element" );
        [color="#0000ff"]if ( element )
        {
            TiXmlElement* child = element->FirstChildElement( [color="#ff00ff"]"Child" );
            [color="#0000ff"]if ( child )
            {
                TiXmlElement* child2 = child->NextSiblingElement( [color="#ff00ff"]"Child" );
                [color="#0000ff"]if ( child2 )
                {
                    [color="#008000"]// Finally do something useful.
    用句柄的话就不会这么冗长了,使用TiXmlHandle类,前面的代码就会变成这样:
    TiXmlHandle docHandle( &document );
    TiXmlElement* child2 = docHandle.FirstChild( [color="#ff00ff"]"Document" ).FirstChild( [color="#ff00ff"]"Element" ).Child( [color="#ff00ff"]"Child", 1 ).ToElement();
    [color="#0000ff"]if ( child2 )
    {
        [color="#008000"]// do something useful
    这处理起来容易多了。 查阅TiXmlHandle可以得到更多的信息。
    行列追踪
    对于某些应用程序来说,能够追踪节点和属性在它们源文件中的原始位置是很重要的。另外,知道解析错误在源文件中的发生位置可以节省大量时间。
    TinyXML能够追踪所有结点和属性在文本文件中的行列原始位置。TiXmlBase::Row() 和
    TiXmlBase::Column()
    方法返回结点在源文件中的原始位置。正确的制表符号可以经由TiXmlDocument::SetTabSize() 来配置。
    使用与安装
    编译与运行xmltest:
    提供了一个Linux Makefile和一个Windows Visual C++ .dsw 文件。只需要简单地编译和运行,它就会在你的磁盘上生成demotest.xml文件并在屏幕上输出。它还尝试用不同的方法遍历DOM并打印出结点数。
    那个Linux makefile很通用,可以运行在很多系统上——它目前已经在mingw和MacOSX上测试过。你不需要运行 ‘make depend’,因为那些依赖关系已经硬编码在文件里了。
    用于VC6的Windows项目文件
    • tinyxml: tinyxml 库,非STL
    • tinyxmlSTL: tinyxml 库,STL
    • tinyXmlTest: 用于测试的应用程序,非STL
    • tinyXmlTestSTL: 用于测试的应用程序,STL

    Makefile
    在makefile的顶部你可以设置:
    PROFILE,DEBUG,和TINYXML_USE_STL。makefile里有具体描述。
    在tinyxml目录输入“make clean”然后“make”,就可以生成可执行的“xmltest”文件。
    在某一应用程序中使用:
    把tinyxml.cpp,tinyxml.h, tinyxmlerror.cpp, tinyxmlparser.cpp,
    tinystr.cpp, 和 tinystr.h
    添加到你的项目和makefile中。就这么简单,它可以在任何合理的C++适用系统上编译。不需要为TinyXML打开异常或者运行时类型信息支持。
    TinyXML怎么工作
    举个例子可能是最好的办法,理解一下:

    Go to the Toy store!
    Do bills
    它称不上是一个To Do列表,但它已经足够了。像下面这样读取并解析这个文件(叫“demo.xml”)你就能创建一个文档:
    TiXmlDocument doc( [color="#ff00ff"]"demo.xml" );
    doc.LoadFile();
    现在它准备好了,让我们看看其中的某些行和它们怎么与DOM联系起来。
    第一行是一个声明,它会转化成TiXmlDeclaration 类,同时也是文档结点的第一个子结点。
    这是TinyXML唯一能够解析的指令/特殊标签。一般来说指令标签会保存在TiXmlUnknown 以保证在它保存回磁盘时不会丢失这些命令。
    这是一个注释,会成为一个TiXmlComment对象。
    "ToDo"标签定义了一个TiXmlElement 对象。它没有任何属性,但包含另外的两个元素。
    生成另一个TiXmlElement对象,它是“ToDo”元素的子结点。此元素有一个名为“priority”和值为“1”的属性。
    Go to the
    TiXmlText ,这是一个叶子结点,它不能再包含其它结点,是"Item" TiXmlElement的子结点。
    另一个TiXmlElement, 这也是“Item”元素的子结点。
    等等
    最后,看看整个对象树:
    TiXmlDocument "demo.xml"
    TiXmlDeclaration "version=’1.0′" "standalone=no"
    TiXmlComment " Our to do list data"
    TiXmlElement "ToDo"
    TiXmlElement "Item" Attribtutes: priority = 1
    TiXmlText "Go to the "
    TiXmlElement "bold"
    TiXmlText "Toy store!"
    TiXmlElement "Item" Attributes: priority=2
    TiXmlText "Do bills"
    文档
    本文档由Doxygen使用‘dox’配置文件生成。
    许可证
    TinyXML基于zlib许可证来发布:
    本软件按“现状”提供(即现在你看到的样子),不做任何明确或隐晦的保证。由使用此软件所引起的任何损失都决不可能由作者承担。
    只要遵循下面的限制,就允许任何人把这软件用于任何目的,包括商业软件,也允许修改它并自由地重新发布:
    1. 决不能虚报软件的来源;你决不能声称是你是软件的第一作者。如果你在某个产品中使用了这个软件,那么在产品文档中加入一个致谢辞我们会很感激,但这并非必要。
    2. 修改了源版本就应该清楚地标记出来,决不能虚报说这是原始软件。
    3. 本通告不能从源发布版本中移除或做修改。
    参考书目
    万维网联盟是定制XML的权威标准机构,它的网页上有大量的信息。
    权威指南:
    http://www.w3.org/TR/2004/REC-xml-20040204/

    我还要推荐由OReilly出版由Robert Eckstein撰写的"XML Pocket Reference"……这本书囊括了入门所需要的一切。
    捐助者,联系人,还有简史
    非常感谢给我们建议,漏洞报告,意见和鼓励的所有人。它们很有用,并且使得这个项目变得有趣。特别感谢那些捐助者,是他们让这个网站页面生机勃勃。
    有很多人发来漏洞报告和意见,与其在这里一一列出来不如我们试着把它们写到“changes.txt”文件中加以赞扬。
    TinyXML的原作者是Lee Thomason(文档中还经常出现“我”这个词) 。在Yves Berquin,Andrew Ellerton,和tinyXml社区的帮助下,Lee查阅修改和发布新版本。
    我们会很感激你的建议,还有我们想知道你是否在使用TinyXML。希望你喜欢它并觉得它很有用。请邮寄问题,评论,漏洞报告给我们,或者你也可登录网站与我们取得联系:
    www.sourceforge.net/projects/tinyxml

    Lee Thomason, Yves Berquin, Andrew Ellerton
                           
                                           
                            posted @
    2008-10-14 16:52
    茶 阅读(363) |
    评论 (0)
    |
    编辑

    收藏
                           
                   
           
           
                   
                              
           
    2008年10月13日
           
                   
                           
    Linux下多线程编程详解
                           
                            http://www.yuanma.org/data/2007/0921/article_2859.htm
    线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。
    现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。
      为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。
    使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空
    间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址
    空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
    据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
      使用多线程
    的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方
    便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他
    一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最
    需要注意的地方。
      除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
      1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
      2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
      3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
      下面我们先来尝试编写一个简单的多线程程序。
      简单的多线程编程
    Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要
    使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特
    有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序
    pthread_create.c。
    一个重要的线程创建函数原型:
    #include
    int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr, void *(*start_rtn)(void),void *restrict arg);
        返回值:若是成功建立线程返回0,否则返回错误的编号
        形式参数:
                    pthread_t *restrict tidp 要创建的线程的线程id指针
                    const pthread_attr_t *restrict attr 创建线程时的线程属性
                    void* (start_rtn)(void) 返回值是void类型的指针函数
                    void *restrict arg   start_rtn的行参
                   
    例程1:                                
        功能:创建一个简单的线程
        程序名称:pthread_create.c         
    /********************************************************************************************
    **    Name:pthread_create.c
    **    Used to study the multithread programming in Linux OS
    **    Author:zeickey
    **    Date:2006/9/16        
    **    Copyright (c) 2006,All Rights Reserved!
    *********************************************************************************************/
    #include
    #include
    void *myThread1(void)
    {
        int i;
        for (i=0; i
    #include
    #include
    void *create(void *arg)
    {
        int *num;
        num=(int *)arg;
        printf("create parameter is %d \n",*num);
        return (void *)0;
    }
    int main(int argc ,char *argv[])
    {
        pthread_t tidp;
        int error;
       
        int test=4;
        int *attr=&test;
       
        error=pthread_create(&tidp,NULL,create,(void *)attr);
        if(error)
            {
            printf("pthread_create is created is not created ... \n");
            return -1;
            }
        sleep(1);
        printf("pthread_create is created ...\n");
        return 0;        
    }
        编译方法:
    gcc -lpthread pthread_int.c -Wall
        执行结果:
    create parameter is 4
    pthread_create is created is  created ...
        例程总结:
        可以看出来,我们在main函数中传递的整行指针,传递到我们新建的线程函数中。
        在上面的例子可以看出来我们向新的线程传入了另一个线程的int数据,线程之间还可以传递字符串或是更复杂的数据结构。
    例程3:
        程序功能:向新建的线程传递字符串
            程序名称:pthread_string.c
    /********************************************************************************************
    **    Name:pthread_string.c
    **    Used to study the multithread programming in Linux OS
    **    Pass a ‘char*‘ parameter to the thread.
    **    Author:zeickey
    **    Date:2006/9/16        
    **    Copyright (c) 2006,All Rights Reserved!
    *********************************************************************************************/
    #include
    #include
    #include
    void *create(void *arg)
    {
        char *name;
        name=(char *)arg;
        printf("The parameter passed from main function is %s  \n",name);
        return (void *)0;
    }
    int main(int argc, char *argv[])
    {
        char *a="zieckey";
        int error;
        pthread_t tidp;
        error=pthread_create(&tidp, NULL, create, (void *)a);
        if(error!=0)
        {
            printf("pthread is not created.\n");
            return -1;
        }
        sleep(1);
        printf("pthread is created... \n");
        return 0;
    }   
      编译方法:
    gcc -Wall pthread_string.c -lpthread
        执行结果:
    The parameter passed from main function is zieckey  
    pthread is created...
        例程总结:
        可以看出来main函数中的字符串传入了新建的线程中。
    例程4:
        程序功能:向新建的线程传递字符串
            程序名称:pthread_struct.c
    /********************************************************************************************
    **    Name:pthread_struct.c
    **    Used to study the multithread programming in Linux OS
    **    Pass a ‘char*‘ parameter to the thread.
    **    Author:zeickey
    **    Date:2006/9/16        
    **    Copyright (c) 2006,All Rights Reserved!
    *********************************************************************************************/
    #include
    #include
    #include
    #include
    struct menber
    {
        int a;
        char *s;
    };
    void *create(void *arg)
    {
        struct menber *temp;
        temp=(struct menber *)arg;
        printf("menber->a = %d  \n",temp->a);
        printf("menber->s = %s  \n",temp->s);
        return (void *)0;
    }
    int main(int argc,char *argv[])
    {
        pthread_t tidp;
        int error;
        struct menber *b;
        b=(struct menber *)malloc( sizeof(struct menber) );
        b->a = 4;
        b->s = "zieckey";
        error = pthread_create(&tidp, NULL, create, (void *)b);
        if( error )
        {
            printf("phread is not created...\n");
            return -1;
        }
        sleep(1);
        printf("pthread is created...\n");
        return 0;
    }
      编译方法:
    gcc -Wall pthread_struct.c -lpthread
        执行结果:
    menber->a = 4  
    menber->s = zieckey  
    pthread is created...
        例程总结:
        可以看出来main函数中的一个结构体传入了新建的线程中。
        线程包含了标识进程内执行环境必须的信息。他集成了进程中的所有信息都是对线程进行共享的,包括文本程序、程序的全局内存和堆内存、栈以及文件描述符。
       
    例程5:
        程序目的:验证新建立的线程可以共享进程中的数据
        程序名称:pthread_share.c  
    /********************************************************************************************
    **    Name:pthread_share_data.c
    **    Used to study the multithread programming in Linux OS
    **    Pass a ‘char*‘ parameter to the thread.
    **    Author:zeickey
    **    Date:2006/9/16        
    **    Copyright (c) 2006,All Rights Reserved!
    *********************************************************************************************/
    #include
    #include
    #include
    static int a=4;
    void *create(void *arg)
    {
        printf("new pthread ... \n");
        printf("a=%d  \n",a);
        return (void *)0;
    }
    int main(int argc,char *argv[])
    {
        pthread_t tidp;
        int error;
       
        a=5;
        error=pthread_create(&tidp, NULL, create, NULL);
        if(error!=0)
        {
            printf("new thread is not create ... \n");
            return -1;
        }
       
        sleep(1);
       
        printf("new thread is created ... \n");
        return 0;
    }
       
      编译方法:
    gcc -Wall pthread_share_data.c -lpthread
        执行结果:
    new pthread ...
    a=5  
    new thread is created ...
        例程总结:
    可以看出来,我们在主线程更改了我们的全局变量a的值的时候,我们新建立的线程则打印出来了改变的值,可以看出可以访问线程所在进程中的数据信息。
    2、线程的终止
        如果进程中任何一个线程中调用exit,_Exit,或者是_exit,那么整个进程就会终止,
        与此类似,如果信号的默认的动作是终止进程,那么,把该信号发送到线程会终止进程。
        线程的正常退出的方式:
           (1) 线程只是从启动例程中返回,返回值是线程中的退出码
           (2) 线程可以被另一个进程进行终止
           (3) 线程自己调用pthread_exit函数
        两个重要的函数原型:
    #include
    void pthread_exit(void *rval_ptr);
    /*rval_ptr 线程退出返回的指针*/
    int pthread_join(pthread_t thread,void **rval_ptr);
       /*成功结束进程为0,否则为错误编码*/
        例程6
        程序目的:线程正常退出,接受线程退出的返回码
        程序名称:pthread_exit.c
    /********************************************************************************************
    **    Name:pthread_exit.c
    **    Used to study the multithread programming in Linux OS
    **    A example showing a thread to exit and with a return code.
    **    Author:zeickey
    **    Date:2006/9/16        
    **    Copyright (c) 2006,All Rights Reserved!
    *********************************************************************************************/
    #include
    #include
    #include
    void *create(void *arg)
    {
        printf("new thread is created ... \n");
        return (void *)8;
    }
    int main(int argc,char *argv[])
    {
        pthread_t tid;
        int error;
        void *temp;
        error = pthread_create(&tid, NULL, create, NULL);
        if( error )
        {
            printf("thread is not created ... \n");
            return -1;
        }
        error = pthread_join(tid, &temp);
        if( error )
        {
            printf("thread is not exit ... \n");
            return -2;
        }
       
        printf("thread is exit code %d \n", (int )temp);
        return 0;
    }
      编译方法:
    gcc -Wall pthread_exit.c -lpthread
        执行结果:
    new thread is created ...
    thread is exit code 8
        例程总结:
    可以看出来,线程退出可以返回线程的int数值。线程退出不仅仅可以返回线程的int数值,还可以返回一个复杂的数据结构。
        例程7
        程序目的:线程结束返回一个复杂的数据结构
        程序名称:pthread_return_struct.c
    #include
    #include
    #include
    struct menber
    {
        int a;
        char *b;
    }temp={8,"zieckey"};
    void *create(void *arg)
    {
        printf("new thread ... \n");
        return (void *)&temp;
    }
    int main(int argc,char *argv[])
    {
        int error;
        pthread_t tid;
        struct menber *c;
        error = pthread_create(&tid, NULL, create, NULL);
       
        if( error )
        {
            printf("new thread is not created ... \n");
            return -1;
        }
        printf("main ... \n");
        error = pthread_join(tid,(void *)&c);
        if( error )
        {
            printf("new thread is not exit ... \n");
            return -2;
        }
        printf("c->a = %d  \n",c->a);
        printf("c->b = %s  \n",c->b);
        sleep(1);
        return 0;
    }
      编译方法:
    gcc -Wall pthread_return_struct.c -lpthread
        执行结果:
    main ...
    new thread ...
    c->a = 8
    c->b = zieckey
    例程总结:
    一定要记得返回的数据结构要是在这个数据要返回的结构没有释放的时候应用,
    如果数据结构已经发生变化,那返回的就不会是我们所需要的,而是脏数据
    3、线程标识
        函数原型:
       
    #include
    pthread_t pthread_self(void);
    pid_t getpid(void);
        getpid()用来取得目前进程的进程识别码,函数说明
        例程8
        程序目的:实现在新建立的线程中打印该线程的id和进程id
        程序名称:pthread_id.c
       
    /********************************************************************************************
    **    Name:pthread_id.c
    **    Used to study the multithread programming in Linux OS.
    **    Showing how to get the thread's tid and the process's pid.
    **    Author:zeickey
    **    Date:2006/9/16        
    **    Copyright (c) 2006,All Rights Reserved!
    *********************************************************************************************/
    #include
    #include
    #include  /*getpid()*/
    void *create(void *arg)
    {
        printf("New thread .... \n");
        printf("This thread's id is %u  \n", (unsigned int)pthread_self());
        printf("The process pid is %d  \n",getpid());
        return (void *)0;
    }
    int main(int argc,char *argv[])
    {
        pthread_t tid;
        int error;
        printf("Main thread is starting ... \n");
        error = pthread_create(&tid, NULL, create, NULL);
        if(error)
        {
            printf("thread is not created ... \n");
            return -1;
        }
        printf("The main process's pid is %d  \n",getpid());
        sleep(1);
        return 0;
    }
        编译方法:
       
    gcc -Wall -lpthread pthread_id.c
        执行结果:
    Main thread is starting ...
    The main process's pid is 3307  
    New thread ....
    This thread's id is 3086347152  
    The process pid is 3307  
                           
                                           
                            posted @
    2008-10-13 15:35
    茶 阅读(53) |
    评论 (0)
    |
    编辑

    收藏
                           
                   
           
                   
                           
    字节对齐详解
                           
                            http://www.yuanma.org/data/2006/0723/article_1213.htm
    其实字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:

       1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
       2) 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;例如上面第二个结构体变量的地址空间。
        3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。例如上面第一个结构体变量。
    一.什么是字节对齐,为什么要对齐?
       
    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特
    定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
       
    对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问
    一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对
    数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那
    么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数
    据。显然在读取效率上下降很多。
    二.字节对齐对程序的影响:
        先让我们看几个例子吧(32bit,x86环境,gcc编译器):
    设结构体如下定义:
    struct A
    {
        int a;
        char b;
        short c;
    };
    struct B
    {
        char b;
        int a;
        short c;
    };
    现在已知32位机器上各种数据类型的长度如下:
    char:1(有符号无符号同)   
    short:2(有符号无符号同)   
    int:4(有符号无符号同)   
    long:4(有符号无符号同)   
    float:4    double:8
    那么上面两个结构大小如何呢?
    结果是:
    sizeof(strcut A)值为8
    sizeof(struct B)的值却是12
    结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
    之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
    #pragma pack (2) /*指定按2字节对齐*/
    struct C
    {
        char b;
        int a;
        short c;
    };
    #pragma pack () /*取消指定对齐,恢复缺省对齐*/
    sizeof(struct C)值是8。
    修改对齐值为1:
    #pragma pack (1) /*指定按1字节对齐*/
    struct D
    {
        char b;
        int a;
        short c;
    };
    #pragma pack () /*取消指定对齐,恢复缺省对齐*/
    sizeof(struct D)值为7。
    后面我们再讲解#pragma pack()的作用.
    三.编译器是按照什么样的原则进行对齐的?
        先让我们看四个重要的基本概念:
    [color="#0000ff"]1.数据类型自身的对齐值:
      对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
    [color="#3300ff"]2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
    [color="#0000ff"]3.指定对齐值:#pragma pack (value)时的指定对齐值value。
    [color="#0000ff"]4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。

    了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是
    表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数
    据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数
    倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。
    例子分析:
    分析例子B;
    struct B
    {
        char b;
        int a;
        short c;
    };

    设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定
    对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,
    所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为
    2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的
    都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,
    0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B
    共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了,
    因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那
    么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一
    个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其
    自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只
    是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
    同理,分析上面例子C:
    #pragma pack (2) /*指定按2字节对齐*/
    struct C
    {
        char b;
        int a;
        short c;
    };
    #pragma pack () /*取消指定对齐,恢复缺省对齐*/

    一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=
    0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续
    字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
    在0x0006、0x0007中,符合
    0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C
    只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.
    四.如何修改编译器的默认对齐值?
    1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
    2.在编码时,可以这样动态修改:#pragma pack .[color="#ff0000"]注意:是pragma而不是progma.
    五.针对字节对齐,我们在编程中如何考虑?
       
    如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照
    类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做
    法是显式的插入reserved成员:
             struct A{
               char a;
               char reserved[3];//使用空间换时间
               int b;
    }
    reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.
    六.字节对齐可能带来的隐患:
        代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
    unsigned int i = 0x12345678;
    unsigned char *p=NULL;
    unsigned short *p1=NULL;
    p=&i;
    *p=0x00;
    p1=(unsigned short *)(p+1);
    *p1=0x0000;
    最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
    在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.
    七.如何查找与字节对齐方面的问题:
    如果出现对齐或者赋值问题首先查看
    1. 编译器的big little端设置
    2. 看这种体系本身是否支持非对齐访问
    3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。
    八.相关文章:转自
    http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx
    ARM下的对齐处理
    from DUI0067D_ADS1_2_CompLib
    3.13 type  qulifiers
    有部分摘自ARM编译器文档对齐部分
    对齐的使用:
    1.__align(num)
       这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时
       就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。
       这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节
       对齐,但是不能让4字节的对象2字节对齐。
       __align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。
       
    2.__packed
      __packed是进行一字节对齐
      1.不能对packed的对象进行对齐
      2.所有对象的读写访问都进行非对齐访问
      3.float及包含float的结构联合及未用__packed的对象将不能字节对齐
      4.__packed对局部整形变量无影响
      5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定
      义为packed。
         __packed int* p;  //__packed int 则没有意义
      6.对齐或非对齐读写访问带来问题
      __packed struct STRUCT_TEST
    {
      char a;
      int b;
      char c;
    }  ;    //定义如下结构此时b的起始地址一定是不对齐的
             //在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]
    //将下面变量定义成全局静态不在栈上
    static char* p;
    static struct STRUCT_TEST a;
    void Main()
    {
    __packed int* q;  //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以
    p = (char*)&a;         
    q = (int*)(p+1);      

    *q = 0x87654321;
    /*   
    得到赋值的汇编指令很清楚
    ldr      r5,0x20001590 ; = #0x12345678
    [0xe1a00005]   mov      r0,r5
    [0xeb0000b0]   bl       __rt_uwrite4  //在此处调用一个写4byte的操作函数
          
    [0xe5c10000]   strb     r0,[r1,#0]   //函数进行4次strb操作然后返回保证了数据正确的访问
    [0xe1a02420]   mov      r2,r0,lsr #8
    [0xe5c12001]   strb     r2,[r1,#1]
    [0xe1a02820]   mov      r2,r0,lsr #16
    [0xe5c12002]   strb     r2,[r1,#2]
    [0xe1a02c20]   mov      r2,r0,lsr #24
    [0xe5c12003]   strb     r2,[r1,#3]
    [0xe1a0f00e]   mov      pc,r14
    */
    /*
    如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败
    [0xe59f2018]   ldr      r2,0x20001594 ; = #0x87654321
    [0xe5812000]   str      r2,[r1,#0]
    */
    //这样可以很清楚的看到非对齐访问是如何产生错误的
    //以及如何消除非对齐访问带来问题
    //也可以看到非对齐访问和对齐访问的指令差异导致效率问题

                           
                                           
                            posted @
    2008-10-13 09:30
    茶 阅读(20) |
    评论 (0)
    |
    编辑

    收藏
                           
                   
           
           
                   
                              
           
    2008年10月9日
           
                   
                           
    进程间通讯简介
                           
                            http://bbs.chinaunix.net/viewthread.php?tid=1130381
    所谓进程间通讯,顾名思义,就是在2个(多数情况下)或多个进程间传递信息。方法大致如下几种:
         1,  文件(file),匿名管道(anonymous pipe),命名管道(named pipe),信号(signal).
             2、  System V IPC 包括消息队列(message queue),共享内存(shared memory),信号量(semaphore)。这种形式的ipc首先在UNIX分支system V中使用,现在多数unix系统都支持。
    文件形式的IPC:
    进程(process) A写信息到文件1,进程B读文件1。文件的内容,由进程自己决定。
    匿名管道:
    command1 args1 | command2 args2. 最常见的例子:ls –l |more
    由于管道操作由shell代替完成,没有产生有名字的实体,所以称为匿名管道。
    Shell做的事情是调用pipe(),产生一个管道,然后把command1的输出连接到管道的出入端,把command2的输入连接到管道的输出端。
    命名管道
    首先,建立一个特殊文件,mkfifo pipe1或者mknod fifo1 p
    然后,就当作正常文件读写pipe1。例如: ls > fifo1 (写入)。
    while read a
    do
       echo $a
    done    (读出)
    由于产生有名字的实体,所以被称为命名管道。
    信号:
    简单的用法: kill –USER2
    pid,也就是通过kill()系统调用或者kill命令,发送信号到别的进程。各个进程对于信号的处理过程是自己定义的(除了9,也就是KILL是强制
    的)。比如自己可以忽略HUP,TERM,INT(按control-C), 等。
    消息队列(message queue)
    消息队列,是一个队列的结构,队列里面的内容由用户进程自己定义。实际上,队列里面记录的是指向用户自定义结构的指针和结构的大小。要使用message
    queue,首先要通过系统调用(msgget)产生一个队列,然后,进程可以用msgsnd发送消息到这个队列,消息就是如上所说的结构。别的进程用
    msgrcv读取。消息队列一旦产生,除非明确的删除(某个有权限的进程或者用ipcrm命令)或者系统重启。否则,产生的队列会一直保留在系统中。而
    且,只要有权限,就可以对队列进行操作。消息队列和管道很相似,实际上,管道就是用户消息为1个字节的队列。
    ipcs –aq命令可以查看message queue的状况:
    Message Queues:
    T      ID        KEY    MODE         OWNER    GROUP  CREATOR   CGROUP
    CBYTES  QNUM QBYTES  LSPID  LRPID  STIME    RTIME    CTIME
    q     256 0x417d0896 --rw-------      root   daemon     root   daemon
        0     0  16384  97737 210466 14:31:14 14:31:14  9:52:53
    其中:
    T: 类型, q 表明这是个消息队列
    ID: 用户自己定义的,在调用msgget时传送的参数。
    Key: 系统返还的全局唯一的ID。
    Mode: 权限,含义和文件权限基本一致
    Owner, group: 队列建立者的名字和组
    CREATOR, CGROUP:队列建立者和组的ID
    CBYTES : 目前queue在队列里的字节数
    QNUM, 目前queue在队列里的消息数
    QBYTES: 队列中消息最大允许字节数
    LSPID: 最后发送者PID
    LRPID: 最后接受者PID
    STIME: 最后发送时间
    RTIME: 最后接受时间。.
    CTIME: 建立或者最后修改的时间
    共享内存(shared memory)
    共享内存是一段可以被多个进程共享的内存段。首先,用shmget系统调用产生指定大小的共享内存段,然后需要访问此共享内存的进程调用shmat系统调
    用,把这个内存段附加到自己的地址空间,然后就可以像访问自己私有的内存一样访问这个内存段了。等到访问完毕,用shmdt脱离。同message
    queue一样,共享内存一旦产生,除非明确的删除(某个有权限的进程或者用ipcrm命令)或者系统重启。否则,产生的共享内存会一直保留在系统中。而
    且,只要有权限,就可以对共享内存进行操作。共享内存的内容由进程自己定义。为了防止多个进程在同一时间写同样一段共享内存,一般程序会使用信号量来控制
    对某一段地址的读写。
    ipcs –am命令可以查看share memory的状况:
    Shared Memory:
    T      ID        KEY    MODE         OWNER    GROUP  CREATOR   CGROUP
    NATTCH                SEGSZ   CPID   LPID   ATIME    DTIME    CTIME
    m     258          0 --rw-r-----    oracle      dba   oracle      dba
       12              8388608 106303 106329 16:28:54 16:48:36 16:28:49
    T: 类型 m 表明这是个共享内存
    ID: 用户自己定义的,在调用shmget时传送的参数。
    Key: 系统返还的全局唯一的ID。
    Mode: 权限,含义和文件权限基本一致
    Owner, group: 队列建立者的名字和组
    CREATOR, CGROUP:队列建立者和组的ID
    NATTCH: 有几个进程挂接(attach)在这段共享内存上
    SEGSZ: 共享内存段大小(字节)
    CPID: 产生者PID
    LPID: 最后挂接(attach)或者脱离(detach)者PID
    ATIME: 最后挂接(attach)时间
    DTIME: 最后脱离(detach)时间。.
    CTIME: 建立或者最后修改的时间
    信号量(semaphore)
    在操作系统中,有些资源数量是有限的,在同一时间,只能由有限(一个或几个)的进程使用和访问。例如磁带机,同一时间,只能由一个进程使用。这样的资源被
    称为关键(critical)资源。信号量就是用来记录关键资源的使用情况的。首先,利用系统调用semget产生一个信号量。当需要使用关键资源时,调
    用semop,传递的参数为需要使用的资源的数量,例如2个,参数就为+2。如果这个资源有2个或者更多可用,进程就获得了使用权,否则就必须等待,直到
    有足够的资源可用。当进程使用资源结束的时候,也用semop释放关键资源。参数为需要释放的数量,例如2,参数为-2。同message
    queue一样,共信号量一旦产生,除非明确的删除(某个有权限的进程或者用ipcrm命令)或者系统重启。否则,信号量会一直保留在系统中。而且,只要
    有权限,就可以对其进行操作。
    ipcs –as命令可以查看Semaphore的状况:
    Semaphores:
    T      ID        KEY    MODE         OWNER    GROUP  CREATOR   CGROUP NSEMS   OTIME    CTIME
    s       0 0x696e6974 --ra-r--r--      root   system     root   system     8  9:52:53  9:59:30
    T: 类型 s 表明这是个信号量
    ID: 用户自己定义的,在调用semget时传送的参数。
    Key: 系统返还的全局唯一的ID。
    Mode: 权限,含义和文件权限基本一致
    Owner, group: 队列建立者的名字和组
    CREATOR, CGROUP:队列建立者和组的ID
    NSEMS: 本信号量上信号的数量。
    OTIME: 最后一次操作(semop)的时间
    CTIM: 建立或者最后修改的时间
                           
                                           
                            posted @
    2008-10-09 11:20
    茶 阅读(102) |
    评论 (0)
    |
    编辑

    收藏
                           
                   
           
                   
                           
    System V IPC 之 Message Queue
                           
                            http://www.idcnews.net/html/edu/linux/20080407/264092.html
    在APUE 14.7节对消息队列的讲解中,最后一段说“我们得出的结论是:在新的应用程式中不应当再使用他们。”
      
        虽然在新的应用程式中不应该再使用消息队列,我也没有怎么使用过System V IPC总觉得在UNIX/Linux编程中少了什么,也许学习一下System V IPC对我的自信心会有相当大的帮助,从此我也敢讲我知道如何使用IPC了。
    先把各个函数原形列出。
        #include
        #include
        #include
      
        int msgget(key_t key, int msgflag);
        int msgsnd(int msgid, struct msgbuf *msgp, size_t msgsz, int msgflag);
        ssize_t msgrcv(int msgid, struct msgbuf *msgp, size_t msgsz, long msgtype, int msgflag);
        int msgctl(int msgid, int cmd, struct msqid_ds *buf);
      
       
    msgget()用来创建Message Queue(服务端)或和一个已建立的Message
    Queue连接(客户端)。key,指定用来生成message
    id的关键字,msgflag和open()的flags很相似,可用IPC_CREAT, IPC_EXECL, S_IRUSR等。
      
        在服务端,可用IPC_PRIVATE(或0)来指定key值,来生成一个新的Message Queue,或使用指定的key值(32位的无符号数),或使用ftok()来生成一个key。
        #include
        #include
        key_t ftok(const char *pathname, int proj_id);
      
       
    在客户端,能够直接使用服务端生成的message
    id(通过某些途径传送,如文档,父子进程),也能够用msgget通过和服务端使用相同的key值来生成相同的message
    id,但不能使用IPC_PRIVATE(或0),msgflag也不能使用IPC_CREAT。
        Return Value: Sucess return value is the message id(non-negative integer), otherwise -1 return.
      
        msgsnd()用来发送消息。
        struct msgbuf {
                long mtype;
                char mtext[1];
        };
        msgsz的计算方法: msgsz = sizeof(msgbuf) - sizeof(long);
        msgflag有一个标志:IPC_NOWAIT。当消息队列已满(可能是消息总数达到了限制值,也可能是队列中字节总数达到了限制值),立即出错返回,假如没有指定,则阻塞。
      
    msgrcv()用来接收消息。msgtype用来指定读取的消息类型。msgtype == 0, 返回第一个消息; msgtype >
    0, 返回消息类型为msgtype的消息;msgtype
                           
                                           
                            posted @
    2008-10-09 09:03
    茶 阅读(129) |
    评论 (0)
    |
    编辑

    收藏
                           
                   
           
           
                   
                              
           
    2008年10月8日
           
                   
                           
    Linux下的多进程编程初步
                           
                            http://www.bccn.net/Article/czxt/linux/200511/1037.html
    文章摘要:
       多线程程序设计的概念早在六十年代就被提出,但直到八十年代中期,Unix系统中才引入多线程机制,如今,由于自身的许多优点,多线程编程已经得到了广泛的应用。本文我们将介绍在Linux下编写多进程和多线程程序的一些初步知识。
    --------------------------------------------------------------------------------
    正文:
    Linux下的多进程编程初步
    1 引言
    对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一:它执行一次却返回两个值。fork函数是Unix系统最杰出的成就
    之一,它是七十年代UNIX早期的开发者经过长期在理论和实践上的艰苦探索后取得的成果,一方面,它使操作系统在进程管理上付出了最小的代价,另一方面,
    又为程序员提供了一个简洁明了的多进程方法。与DOS和早期的Windows不同,Unix/Linux系统是真正实现多任务操作的系统,可以说,不使用
    多进程编程,就不能算是真正的Linux环境下编程。
       多线程程序设计的概念早在六十年代就被提出,但直到八十年代中期,Unix系统中才引入多线程机制,如今,由于自身的许多优点,多线程编程已经得到了广泛的应用。
       下面,我们将介绍在Linux下编写多进程和多线程程序的一些初步知识。
    2 多进程编程
    什么是一个进程?进程这个概念是针对系统而不是针对用户的,对用户来说,他面对的概念是程序。当用户敲入命令执行一个程序的时候,对系统而言,它将启动一
    个进程。但和程序不同的是,在这个进程中,系统可能需要再启动一个或多个进程来完成独立的多个任务。多进程编程的主要内容包括进程控制和进程间通信,在了
    解这些之前,我们先要简单知道进程的结构。
      2.1 Linux下进程的结构
       Linux下一个进程在内存里有三部分的数据,就是"代码段"、"堆栈段"和"数据段"。其实学过汇编语言的人一定知道,一般的CPU都有上述三种段寄存器,以方便操作系统的运行。这三个部分也是构成一个完整的执行序列的必要的部分。
    "代码段",顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。"堆栈段"存放的就是子程
    序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空
    间)。这其中有许多细节问题,这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。
      2.2 Linux下的进程控制
    在传统的Unix环境下,有两个基本的操作用于创建和修改进程:函数fork(
    )用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;函数族exec(
    )用来启动另外的进程以取代当前运行的进程。Linux的进程控制和传统的Unix进程控制基本一致,只在一些细节的地方有些区别,例如在Linux系统
    中调用vfork和fork完全相同,而在有些版本的Unix系统中,vfork调用有不同的功能。由于这些差别几乎不影响我们大多数的编程,在这里我们
    不予考虑。
       2.2.1 fork( )
       fork在英文中是"分叉"的意思。为什么取这个名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就"分叉"了,所以这个名字取得很形象。下面就看看如何具体使用fork,这段程序演示了使用fork的基本框架:
    void main(){
    int i;
    if ( fork() == 0 ) {
    /* 子进程程序 */
    for ( i = 1; i " );
    fgets( command, 256, stdin );
    command[strlen(command)-1] = 0;
    if ( fork() == 0 ) {
    /* 子进程执行此命令 */
    execlp( command, command );
    /* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/
    perror( command );
    exit( errorno );
    }
    else {
    /* 父进程, 等待子进程结束,并打印子进程的返回值 */
    wait ( &rtn );
    printf( " child process return %d\n",. rtn );
    }
    }
    }
    此程序从终端读入命令并执行之,执行完成后,父进程继续等待从终端读入命令。熟悉DOS和WINDOWS系统调用的朋友一定知道DOS/WINDOWS也
    有exec类函数,其使用方法是类似的,但DOS/WINDOWS还有spawn类函数,因为DOS是单任务的系统,它只能将"父进程"驻留在机器内再执
    行"子进程",这就是spawn类的函数。WIN32已经是多任务的系统了,但还保留了spawn类函数,WIN32中实现spawn函数的方法同前述
    UNIX中的方法差不多,开设子进程后父进程等待子进程结束后才继续运行。UNIX在其一开始就是多任务的系统,所以从核心角度上讲不需要spawn类函
    数。
    在这一节里,我们还要讲讲system()和popen()函数。system()函数先调用fork(),然后再调用exec()来执行用户的登录
    shell,通过它来查找可执行文件的命令并分析参数,最后它么使用wait()函数族之一来等待子进程的结束。函数popen()和函数
    system()相似,不同的是它调用pipe()函数创建一个管道,通过它来完成程序的标准输入和标准输出。这两个函数是为那些不太勤快的程序员设计
    的,在效率和安全方面都有相当的缺陷,在可能的情况下,应该尽量避免。
      2.3 Linux下的进程间通信
    详细的讲述进程间通信在这里绝对是不可能的事情,而且笔者很难有信心说自己对这一部分内容的认识达到了什么样的地步,所以在这一节的开头首先向大家推荐著
    名作者Richard Stevens的著名作品:《Advanced Programming in the UNIX
    Environment》,它的中文译本《UNIX环境高级编程》已有机械工业出版社出版,原文精彩,译文同样地道,如果你的确对在Linux下编程有浓
    厚的兴趣,那么赶紧将这本书摆到你的书桌上或计算机旁边来。说这么多实在是难抑心中的景仰之情,言归正传,在这一节里,我们将介绍进程间通信最最初步和最
    最简单的一些知识和概念。
    首先,进程间通信至少可以通过传送打开文件来实现,不同的进程通过一个或多个文件来传递信息,事实上,在很多应用系统里,都使用了这种方法。但一般说来,
    进程间通信(IPC:InterProcess
    Communication)不包括这种似乎比较低级的通信方法。Unix系统中实现进程间通信的方法很多,而且不幸的是,极少方法能在所有的Unix系
    统中进行移植(唯一一种是半双工的管道,这也是最原始的一种通信方式)。而Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信
    方法:管道、消息队列、共享内存、信号量、套接口等等。下面我们将逐一介绍。
       2.3.1 管道
       管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。
       无名管道由pipe()函数创建:
       #include  
       int pipe(int filedis[2]);
       参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。
    #define INPUT 0
    #define OUTPUT 1
    void main() {
    int file_descriptors[2];
    /*定义子进程号 */
    pid_t pid;
    char buf[256];
    int returned_count;
    /*创建无名管道*/
    pipe(file_descriptors);
    /*创建子进程*/
    if((pid = fork()) == -1) {
    printf("Error in fork\n");
    exit(1);
    }
    /*执行子进程*/
    if(pid == 0) {
    printf("in the spawned (child) process...\n");
    /*子进程向父进程写数据,关闭管道的读端*/
    close(file_descriptors[INPUT]);
    write(file_descriptors[OUTPUT], "test data", strlen("test data"));
    exit(0);
    } else {
    /*执行父进程*/
    printf("in the spawning (parent) process...\n");
    /*父进程从管道读取子进程写的数据,关闭管道的写端*/
    close(file_descriptors[OUTPUT]);
    returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
    printf("%d bytes of data received from spawned process: %s\n",
    returned_count, buf);
    }
    }
    在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:
         方式一:mkfifo("myfifo","rw");
         方式二:mknod myfifo p
       生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。下面即是一个简单的例子,假设我们已经创建了一个名为myfifo的有名管道。
      /* 进程一:读有名管道*/
    #include  
    #include  
    void main() {
    FILE * in_file;
    int count = 1;
    char buf[80];
    in_file = fopen("mypipe", "r");
    if (in_file == NULL) {
    printf("Error in fdopen.\n");
    exit(1);
    }
    while ((count = fread(buf, 1, 80, in_file)) > 0)
    printf("received from pipe: %s\n", buf);
    fclose(in_file);
    }
      /* 进程二:写有名管道*/
    #include  
    #include  
    void main() {
    FILE * out_file;
    int count = 1;
    char buf[80];
    out_file = fopen("mypipe", "w");
    if (out_file == NULL) {
    printf("Error opening pipe.");
    exit(1);
    }
    sprintf(buf,"this is test data for the named pipe example\n");
    fwrite(buf, 1, 80, out_file);
    fclose(out_file);
    }
       2.3.2 消息队列
       消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,事实上,它是一种正逐渐被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它,所以,我们对此方式也不再解释,也建议读者忽略这种方式。
       2.3.3 共享内存
    共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行
    读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的将是
    实际的物理内存,在Linux系统下,这只有通过限制Linux系统存取的内存才可以做到,这当然不太实际。常用的方式是通过shmXXX函数族来实现利
    用共享内存进行存储的。
       首先要用的函数是shmget,它获得一个共享存储标识符。
         #include  
         #include  
         #include  
          int shmget(key_t key, int size, int flag);
    这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存。Linux系统内核中每个IPC结构都有的一个非负整数
    的标识符,这样对一个消息队列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的,这个关键字,就是上面第一个函数的
    key。数据类型key_t是在头文件sys/types.h中定义的,它是一个长整形的数据。在我们后面的章节中,还会碰到这个关键字。
       当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中。
       void *shmat(int shmid, void *addr, int flag);
       shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。
    使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存
    储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。
       2.3.4 信号量
       信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
       (1) 测试控制该资源的信号量。
       (2) 若此信号量的值为正,则允许进行使用该资源。进程将进号量减1。
       (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。
       (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
    维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include /linux /sem.h
    中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获
    得一个信号量ID。
       #include  
       #include  
       #include  
       int semget(key_t key, int nsems, int flag);
    key是前面讲过的IPC结构的关键字,它将来决定是创建新的信号量集合,还是引用一个现有的信号量集合。nsems是该集合中的信号量数。如果是创建新
    集合(一般在服务器中),则必须指定nsems;如果是引用一个现有的信号量集合(一般在客户机中)则将nsems指定为0。
       semctl函数用来对信号量进行操作。
       int semctl(int semid, int semnum, int cmd, union semun arg);
       不同的操作是通过cmd参数来实现的,在头文件sem.h中定义了7种不同的操作,实际编程时可以参照使用。
       semop函数自动执行信号量集合上的操作数组。
       int semop(int semid, struct sembuf semoparray[], size_t nops);
       semoparray是一个指针,它指向一个信号量操作数组。nops规定该数组中操作的数量。
       下面,我们看一个具体的例子,它创建一个特定的IPC结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后我们清除信号量。在下面的代码中,函数ftok生成我们上文所说的唯一的IPC关键字。
    #include  
    #include  
    #include  
    #include  
    void main() {
    key_t unique_key; /* 定义一个IPC关键字*/
    int id;
    struct sembuf lock_it;
    union semun options;
    int i;
    unique_key = ftok(".", 'a'); /* 生成关键字,字符'a'是一个随机种子*/
    /* 创建一个新的信号量集合*/
    id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
    printf("semaphore id=%d\n", id);
    options.val = 1; /*设置变量值*/
    semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/
    /*打印出信号量的值*/
    i = semctl(id, 0, GETVAL, 0);
    printf("value of semaphore at index 0 is %d\n", i);
    /*下面重新设置信号量*/
    lock_it.sem_num = 0; /*设置哪个信号量*/
    lock_it.sem_op = -1; /*定义操作*/
    lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/
    if (semop(id, &lock_it, 1) == -1) {
    printf("can not lock semaphore.\n");
    exit(1);
    }
    i = semctl(id, 0, GETVAL, 0);
    printf("value of semaphore at index 0 is %d\n", i);
    /*清除信号量*/
    semctl(id, 0, IPC_RMID, 0);
    }
       2.3.5 套接口
    套接口(socket)编程是实现Linux系统和其他大多数操作系统中进程间通信的主要方式之一。我们熟知的WWW服务、FTP服务、TELNET服务
    等都是基于套接口编程来实现的。除了在异地的计算机进程间以外,套接口同样适用于本地同一台计算机内部的进程间通信。关于套接口的经典教材同样是
    Richard
    Stevens编著的《Unix网络编程:联网的API和套接字》,清华大学出版社出版了该书的影印版。它同样是Linux程序员的必备书籍之一。
    关于这一部分的内容,可以参照本文作者的另一篇文章《设计自己的网络蚂蚁》,那里由常用的几个套接口函数的介绍和示例程序。这一部分或许是Linux进程
    间通信编程中最须关注和最吸引人的一部分,毕竟,Internet
    正在我们身边以不可思议的速度发展着,如果一个程序员在设计编写他下一个程序的时候,根本没有考虑到网络,考虑到Internet,那么,可以说,他的设
    计很难成功。
    3 Linux的进程和Win32的进程/线程比较
       熟悉WIN32编程的人一定知道,WIN32的进程管理方式与Linux上有着很大区别,在UNIX里,只有进程的概念,但在WIN32里却还有一个"线程"的概念,那么Linux和WIN32在这里究竟有着什么区别呢?
    WIN32里的进程/线程是继承自OS/2的。在WIN32里,"进程"是指一个程序,而"线程"是一个"进程"里的一个执行"线索"。从核心上
    讲,WIN32的多进程与Linux并无多大的区别,在WIN32里的线程才相当于Linux的进程,是一个实际正在执行的代码。但是,WIN32里同一
    个进程里各个线程之间是共享数据段的。这才是与Linux的进程最大的不同。
       下面这段程序显示了WIN32下一个进程如何启动一个线程。
    int g;
    DWORD WINAPI ChildProcess( LPVOID lpParameter ){
    int i;
    for ( i = 1; i
                                           
                            posted @
    2008-10-08 15:47
    茶 阅读(37) |
    评论 (0)
    |
    编辑

    收藏
                           
                   
           
           
                   
                              
           
    2008年10月6日
           
                   
                           
    Linux指令篇:档案目录管理--locate
                           
                            http://doc.linuxpk.com/201.html
    名称:locate
      使用权限:所有使用者
      使用方式: locate [-q] [-d ] [--database=]
      locate [-r ] [--regexp=]
      locate [-qv] [-o ] [--output=]
      locate [-e ] [-f ] ] [-c]
      locate [-Vh] [--version] [--help]
      说明:
      locate 让使用者可以很快速的搜寻档案系统内是否有指定的档案。其方法是先建立一个包括系统内所有档案名称及路径的数据库,之后当寻找时就只需查询这个数据库,而不必实际深入档案系统之中了。
      在一般的 distribution 之中,数据库的建立都被放在 contab 中自动执行。一般使用者在使用时只要用
      # locate your_file_name
      的型式就可以了。 参数:
      -u
      -U
      建立数据库,-u 会由根目录开始,-U 则可以指定开始的位置。
      -e
      将
      排除在寻找的范围之外。
      -l
      如果  是 1.则启动安全模式。在安全模式下,使用者不会看到权限无法看到的档案。这会始速度减慢,因为 locate 必须至实际的档案系统中取得档案的权限资料。
      -f
      将特定的档案系统排除在外,例如我们没有到理要把 proc 档案系统中的档案放在数据库中。
      -q
      安静模式,不会显示任何错误讯息。
      -n
      至多显示  个输出。
      -r
      使用正规运算式  做寻找的条件。
      -o
      指定数据库存的名称。
      -d
      指定数据库的路径
      -h
      显示辅助讯息
      -v
      显示更多的讯息
      -V
      显示程序的版本讯息 范例:
      locate chdrv            : 寻找所有叫 chdrv 的档案
      locate -n 100 a.out     : 寻找所有叫  a.out 的档案,但最多只显示 100 个
      locate -u               : 建立数据库
    locate命令可以在搜寻数据库时快速找到档案,数据库由updatedb程序来更新,updatedb是由cron
    daemon周期性建立的,locate命令在搜寻数据库时比由整个由硬盘资料来搜寻资料来得快,但较差劲的是locate所找到的档案若是最近才建立或
    刚更名的,可能会找不到,在内定值中,updatedb每天会跑一次,可以由修改crontab来更新设定值。(etc/crontab)
    locate指定用在搜寻符合条件的档案,它会去储存档案与目录名称的数据库内,寻找合乎范本样式条件的档案或目录录,可以使用特殊字元(如”*”或
    ”?”等)来指定范本样式,如指定范本为kcpa*ner,
    locate会找出所有起始字串为kcpa且结尾为ner的档案或目录,如名称为kcpartner若目录录名称为kcpa_ner则会列出该目录下包括
    子目录在内的所有档案。
      locate指令和find找寻档案的功能类似,但locate是透过update程序将硬盘中的所有档案和
    目录资料先建立一个索引数据库,在执行loacte时直接找该索引,查询速度会较快,索引数据库一般是由操作系统管理,但也可以直接下达update强迫
    系统立即修改索引数据库。
      不过第一次在执行update後再使用locate寻找档案常会失败,此时就要执行slocate
    ˉu该命令(也可执行updatedb指令,其效果相同)来更新slocate数据库,该命令会在/usr/sbin下产生slocate执行档,再由
    locate到此数据库寻找所要找的资料。
                           
                   
                   
                   

    本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/102400/showart_2033414.html
  • 您需要登录后才可以回帖 登录 | 注册

    本版积分规则 发表回复

      

    北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
    未成年举报专区
    中国互联网协会会员  联系我们:huangweiwei@itpub.net
    感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

    清除 Cookies - ChinaUnix - Archiver - WAP - TOP