#深入理解System V原理

2017-11-07 18:40:21来源:CSDN作者:hyj_zkdzslh人点击

分享

深入理解System V原理

1. sysem v消息队列

------------------------------------------------
首先,系统中的消息队列,在内核中会维护一个叫msqid_ds的信息结构

           struct msqid_ds {               struct ipc_perm msg_perm;     /* Ownership and permissions */               struct msg      *msg_frist    /* ptr to first message on queue */               struct msg      *msg_last     /* ptr to last message on queue */               time_t          msg_stime;    /* Time of last msgsnd(2) */               time_t          msg_rtime;    /* Time of last msgrcv(2) */               time_t          msg_ctime;    /* Time of last change */               unsigned long   __msg_cbytes; /* Current number of bytes in                                                queue (nonstandard) */               msgqnum_t       msg_qnum;     /* Current number of messages                                                in queue */               msglen_t        msg_qbytes;   /* Maximum number of bytes                                                allowed in queue */               pid_t           msg_lspid;    /* PID of last msgsnd(2) */               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */           };

这里写图片描述
这个消息队列的链表中,维护这三个消息,消息类型分别为100,200,300. 长度为1, 2 ,3字节。
具体这个消息是如何定义的,我们之后在谈。
看到了这个结构后就可以开始理解后面所有的函数的作用了。

int msgget(key_t key, int oflag);

这个函数的参数和返回值就在这里,不在累赘。
当你用msgget函数创建一个消息队列的时候,在内核中,会对msqid_ds进行初始化。其中有一个叫ipc_perm这个结构体就不得不提起来。

struct ipc_perm{    key_t            key;                        /*ipc的key值*/    uid_t            uid;                       /*共享内存所有者的有效用户ID */    gid_t            gid;                       /* 共享内存所有者所属组的有效组ID*/    uid_t            cuid;                     /* 共享内存创建 者的有效用户ID*/    gid_t            cgid;                     /* 共享内存创建者所属组的有效组ID*/    unsigned short   mode;               /* Permissions + SHM_DEST和SHM_LOCKED标志*/    unsignedshort    seq;                  /* 序列号*/};

其中uid和cuid,gid和cgid会根据当前进程的用户和用户组ID进行初始化
oflag对应的权限会放在mode中,key即为传入的key;
在消息队列ds中一切与操作(msgsnd msgget)有关的会被初始化为0;
讲当前时间放入c_time中 msg_qbytes设置为系统限制值。

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,                      int msgflg);

这两个函数密不可分,所以我放在一起说,看名字就知道一个是发送消息,一个是接受消息。
在这里我们需要自己定义自己的结构体来传递消息。

 struct mesg {    long mesg_tpye;      char mesg_date[BUF_SIZE]; };

在自定义的结构体的时候要注意,第一个成员一定要是long类型的tpye成员,用于存放消息类型。用我自定义的结构体为例子,msgsnd的第二个应该为mesg的指针,第三个应该为strlen(mesg_date)+1。那么来一个更通用的列子吧。

 struct mesg {    long mesg_tpye;    int  mesg_short;      char mesg_date[BUF_SIZE]; };

在这里有两个数据需要传递,那么长度应该怎么写呢?可以是sizeof(mesg) - sizeof(long);
所以这个长度,应该是你要发送的数据长度。也就是再个下图的消息进行初始化,不是吗?
这里写图片描述
在msgrcv中size_t length是一个期望值,就是表示自己可以接受的最大长度的数据。所以你可以用一个宏的长度来接受。
接下来我们继续关注oflag这个参数,这个参数有两个值0和IPC_NOWAIT。
特别注意,在指定非阻塞的情况下,有多种情况可能返回使函数立即返回。
1.消息队列中已经有太多的字节,注意msq_qbytes规定了消息队列的最大字节数。
2.系统中有过多的消息。
这是将返回一个EIDGAIN;
若指定了0,线程就会投入睡眠。如果msqid被删除会返回EIDRM,直到有新的存放空间时醒来。

在msgrcv中有一个type,也需要注意。
type = 0的时候,就直接取出msg_first指向的第一个消息。
type > 0的时候,取出类型为type的第一消息。
type < 0的时候,取出第一个类型小于等于type的绝对值的消息。
注意当接受消息指定的len小于传入的消息的字节数的时候,会返回一个E2BIG的错误。当在oflag中指定了MSG_NOERROR将会截断数据!

 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

cmd一共有三个参数:
1.IPC_RMID 用于删除指定的消息队列,而且当前队列上的所有消息将会被丢弃。在指定这个参数的时候,第三个参数被忽略,你可以传入一个NULL;
2.IPC_SET 设置消息队列的属性,要设置的属性需先存储在buf中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes
3.IPC_STAT:获得msgid的消息队列头数据到buf中
在使用第二参数的时候,需要自己指定一个buff结构对应所需要修改的权限,比如struct ipc_perm info;
同理第三个参数也需要指定一个buff,类型是msqid_ds;

实例代码

这里面有一个用消息队列的服务器的例子。

2. sysem v信号量

------------------------------------------------
首先在内核中会维护一个如下的信息结构。

           struct semid_ds {               struct ipc_perm sem_perm;  /* Ownership and permissions */               time_t          sem_otime; /* Last semop time */               time_t          sem_ctime; /* Last change time */               unsigned long   sem_nsems; /* No. of semaphores in set */               struct sem      *sem_base; /* ptr to array of sem */           };

这里面有一个新的成员,值得一提sem_nsems它表示信号量数组的元素的个数。sem_base结构体指针实际上其实是不存在的(就算存在也是指向内核的一部分区域)。

    struct sem{        ushort_t    semval;     /*sem value, nonnegative */        short       sempid      /*pid of last successful semop(), SETVAL, SETALL */        ushort_t    semncnt;    /*awating semval >current value */        ushort_t    semzcnt;    /*awating semval = 0 */    }

其中semcnt 是等待其值增长的进程数计数,semzcnt是等待其值变0的进程数计数;
这里写图片描述
在这个图片中nsems等于2共有两个成员,一个用0表示,一个用1表示(下标)。

  int semget(key_t key, int nsems, int semflg);

这个函数既可以创建信号量,也可以访问信号量。在创建信号量时候,nsems为要创建的信号量的数量,semflag为其权限:SEM_R和SEM_A分别表示读和改还可以与常见的ipc权限进行按或位。
初始话时与msgget一样初始化semid_ds但是不初始化sem成员结构体。
而且只有在semget创建一个新的信号量的时候,而且成功返回是o_time才会被设置为0。

   int semop(int semid, struct sembuf *sops, size_t nsops);

这个函数用与操作sem信号量。
semid 用于指定目标信号量集
sembuf的结构

    struct sembuf{           unsigned short sem_num;  /* semaphore number */           short          sem_op;   /* semaphore operation */           short          sem_flg;  /* operation flags */           };

其中sem_num 用于指定操作第几个信号量, op是操作数, 通常是+1 -1分别对应pv操作。
P(S):①将信号量S的值减1,即S=S-1; ②如果S=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
V(S):①将信号量S的值加1,即S=S+1;②如果S>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。
sem_op > 0 semval加上 sem_op 的值,并且将semadj减去sem_opd的值,表示进程释放控制的资源;
sem_op = 0 如果没有设置 IPC_NOWAIT,相应的semzcnt就增加1,调用进程进入阻塞,直到信号量的semval值为0;否则进程不回睡眠,直接返回 EAGAIN
sem_op < 0 调用者希望semval的值大于或者等于sem_op的绝对值值。如果semval大于sem_op的绝对值,semval加上 sem_op 的值,并且将sem_op的绝对值加到semadj上。如果不大于若没有设置 IPC_NOWAIT ,则调用进程阻塞,直到资源可用;否则进程直接返回EAGAIN
在操作semadj的时候需要sem_flg指定SEM_UNDO。
而且需要注意的是系统保证op操作是一个原子操作,这个非常重要!!!

 int semctl(int semid, int semnum, int cmd, ...);

...对应union semun arg*

           union semun {               int              val;    /* Value for SETVAL */               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */               unsigned short  *array;  /* Array for GETALL, SETALL */               struct seminfo  *__buf;  /* Buffer for IPC_INFO                                           (Linux-specific) */           };

这个公用体在需要在程序中显示的申明!
PC_STAT

从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中

IPC_SET

设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值

IPC_RMID

从内核中删除信号量集合

GETALL

从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中

GETNCNT

返回当前等待资源的进程个数

GETPID

返回最后一个执行系统调用semop()进程的PID

GETVAL

返回信号量集合内单个信号量的值

GETZCNT

返回当前等待100%资源利用的进程个数

SETALL

与GETALL正好相反

SETVAL

用联合体中val成员的值设置信号量集合中单个信号量的值

下面是一些实例代码
代码

3. sysem v共享内存

------------------------------------------------
首先在内核中会维护这样一个结构。

 struct shmid_ds {               struct ipc_perm shm_perm;    /* Ownership and permissions */               size_t          shm_segsz;   /* Size of segment (bytes) */               time_t          shm_atime;   /* Last attach time */               time_t          shm_dtime;   /* Last detach time */               time_t          shm_ctime;   /* Last change time */               pid_t           shm_cpid;    /* PID of creator */               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */               shmatt_t        shm_nattch;  /* No. of current attaches */               ...};

除了有一个shm_nattch之外,其他成员都是多次使用。
shm_nattch主要与下面两个函数有关。在此就不在赘述shmget和shmctl它们上面的函数有着共同的特性与使用方法。

       void *shmat(int shmid, const void *shmaddr, int shmflg);       int shmdt(const void *shmaddr);

这两个函数分别用于附接和断开进程的地址空间。
shmat
第一个参数,shm_id是由shmget()函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,如果shmaddr 是NULL,系统将自动选择一个合适的地址!如果shmaddr 不是NULL 并且没有指定SHM_RND则此段连接到addr所指定的地址上。 3.如果shmaddr非0 并且指定了SHM_RND 则此段连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上。这里解释一下SHM_RND命令,它的意思是取整,而SHMLAB的意思是低边界地址的倍数,它总是2的乘方,该算式是将地址向下取最近一个SHMLAB的倍数。 除非只计划在一种硬件上运行应用程序(在现在是不太可能的),否则不用指定共享段所连接到的地址。所以一般指定shmaddr为0,以便由内核选择地址。
第三个参数,shm_flg是一组标志位,通常为0。调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
shmdt
参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.

这里有一份shm和sem共同作用搭建服务器的例子大家可以参考下,不懂的可以在下面回复我。
点我下载查看代码

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台