信号量 P V测试详解

信号量

当我们编写的程序使用了线程时,不管它是运行在多用户系统上,多进程系统上,还是运行在多用户多进程系统上,我们通常会发现,程序中存在着一部分临界代码,我们需要确保只有一个进程可以进入这个临界代码并拥有对资源独占的访问权

信号量的定义

最简单的信号量是只能取值0和1的变量,即二进制信号量,这也是信号量最常见的一种形式

pv操作的定义非常简单,假设有一个信号变量sv,如下所示:

p(sv)       如果sv的值大于零,就给它减去1,如果它的值等于0,就挂起该进程的执行

v(sv)     如果有其它进程因等待sv而被挂起,就让它恢复运行,如果没有就加1

Linux的信号量机制

信号量函数定义如下:

#include

int semctl(int sem_id,int sem_num,int command,...);

int semget(key_t key,int num_sems,int sem_flags);

int semop(int sem_id,struct sembuf *sem_ops,size_t num_sem_ops);

参数key的作用很像一个文件名

1.semget函数

semget函数的作用是创建一个新信号或取得一个已有信号量的健

int semget(key_t key,int num_sems,int sem_flags);

第一个参数key是整数值,不相关的进程可以通过它访问一个信号量(两个key值设为相同就可以了),程序对所有信号的访问都是间接的,它先提供一个键,再有系统生成一个相应的信号量标识符

num_sems第二个参数指定需要的信号量数目,它几乎取值都是1

sem_flags参数是一组标志,它与open函数的标志非常相似,它低端的9个比特是该信号的权限

semget函数在成功时返回一个正数,也就是其它信号量要用到的信号量标识符,如果失败返回-1

2.semop函数

semop函数用于改变信号量的值,它的定义如下:

int semop(int sem_id,struct sembuf *sem_ops,size_t num_sem_ops);

第一个参数sem_id是由semget返回的信号量标识符,第二个参数sem_ops是指向一个结构体数组的指针,每个数组元素至少包含如下几个成员

struct sembuf {

short sem_num;

short sem_op;

short sem_flg;

}

第一个成员sem_num是信号量编号,除非你需要使用一组信号量,否则它的取值为0,sem_op成员的值是信号量在一次操作需要改变的值,通常只会用到两个值,一个是-1,也就是p操作,它等待信号量变为可用,一个是+1,也就是v操作

最后一个成员sem_flg通常被设为SEM_UNDO

3.semctl函数

semctl函数的值用来直接控制信号量信息,它的定义如下:

int semctl(int sem_id,int sem_num,int command,...)

第一个参数sem_id是由semget返回的信号量标识符,sem_num参数是信号量的编号,它一般设为0,表示这是一个唯一的信号量,command参数是要采取的动作,如果还有第四个参数,它将是一个union semun结构的,它至少包括如下成员如下:

union semun {

int val;

struct semid_ds *buf;

unsigned short *array;

}

semctl函数中的command参数可以设置许多不同的值,现在介绍连个常用的,如下:

*SETVAL :用来把信号量初始化为一个已知的值,这个值通过union semun中的val成员设置

*IPC_RMID: 用于删除一个已经无需使用的信号量标识符

下面是一个使用信号量的例子:

#include <unistd.h>

#include <stdlib.h>

#include <stdio.h>

 

#include <sys/sem.h>

//#include "semun.h"

//在新版2.6内核中关于union sem_union 这个联合体已经被注释了,需要自己写这个联合体

union semun {int val; struct semid_ds *buf; unsigned short *array;} sem_union;

 

 

static int set_semvalue(void);

static void del_semvalue(void);

static int semaphore_p(void);

static int semaphore_v(void);

 

static int sem_id;

 

int main(int argc, char *argv[])

{

int i;

int pause_time;

char op_char = 'O';

 

srand((unsigned int)getpid());

 

sem_id = semget((key_t)1234,1,0666 | IPC_CREAT);//创建一个新的信号量,IPC_CREAT的作用是:如果信号量不存在,就创建它

 

if (argc > 1) {

if(!set_semvalue()) {//初始化信号量的值为1

fprintf(stderr,"Failed to initialize semaphore\n");

exit(EXIT_FAILURE);

}

op_char = 'X';

sleep(2);

}

 

for(i = 0;i < 10; i++) {

if(!semaphore_p())  exit(EXIT_FAILURE);//p操作把semop成员设为-1就可以了

printf("%c",op_char);fflush(stdout);

pause_time = rand() % 3;

sleep(pause_time);

printf("%c",op_char); fflush(stdout);

if(!semaphore_v())  exit(EXIT_FAILURE);//v操作把semop成员设为1

 

pause_time = rand() % 2;

sleep(pause_time);

}

printf("\n%d - finished\n",getpid());

if(argc > 1) {

sleep(10);

del_semvalue();//回收信号量

}

exit(EXIT_SUCCESS);

}

//把信号量初始化为一个已知值

static int set_semvalue(void)

{

union semun sem_union;

sem_union.val = 1;

if(semctl(sem_id,0,SETVAL,sem_union) == -1) return(0);

return(1);

}

//删除一个信号量

static void del_semvalue(void)

{

union semun sem_union;

if(semctl(sem_id,0,IPC_RMID,sem_union) == -1)

 fprintf(stderr,"Failed to delete semaphore\n");

}

//设置信号量的值P操作

static int semaphore_p(void)

{

struct sembuf sem_b;

 

sem_b.sem_num = 0;

sem_b.sem_op = -1;

sem_b.sem_flg = SEM_UNDO;

if(semop(sem_id,&sem_b,1) == -1) {

fprintf(stderr,"semaphore_p failed\n");

return 0;

}

return 1;

}

//设置信号量的值v操作

static int semaphore_v(void)

{

struct sembuf sem_b;

sem_b.sem_num = 0;

sem_b.sem_op = 1;

sem_b.sem_flg = SEM_UNDO;

if(semop(sem_id,&sem_b,1) == -1) {

fprintf(stderr,"semaphore_v failed\n");

return 0;

}

return 1;

}

 运行这个程序如下:

root@www:/opt/chengxu# ./seml  1  

XXXXXXXXXXXX^C

root@www:/opt/chengxu# ./seml  

OOOOOOOOOOOOOOO^C

共享内存

共享内存是3个ipc机制中的第二个,它允许两个不相关的进程访问同一个逻辑内存,共享内存是在两个进程之间传递数据的一种非常有效的方式,共享内存使用的函数类似于信号量的函数,如下:

#include <sys/shm.h>

 

void *shmat(int shm_id,const void *shm_addr,int shmflg);

int shmctl(int shm_id,int cmd,struct shmid_ds *buf);

int shmdt(const void *shm_addr);

int shmget(key_t key,size_t size,int shmflg);

 

1.shmget函数

我们使用shmget函数来创建共享内存

int shmget(key_t key,size_t size,int shmflg)

与信号量一样,程序需要提供一个参数key,它有效的为共享内存段命名,shmget函数返回一个共享内存标识符,第二个参数size以字节为单位指定需要共享内存容量,第三个参数shmflg包含9个比特的权限标志

2.shmat函数

第一次创建共享内存时,它不能被任何进程访问,要想启动对该共享内存的访问,必须将其连接到一个进程的地址空间中,这项工作由shmat函数来完成,如下:

void *shmat(int shm_id,const void *shm_addr,int shmflg)

第一个参数shm_id是由shmget返回的共享内存标识符

第二个参数shm_addr指定的是共享内存连接到当前进程中的地址位置,它通常是一个空指针,表示让系统来选择共享内存出现的地址

第三个参数shmflg是一组为标志

如果shmat调用成功,它返回一个指向共享内存第一个字节的指针,如果失败返回-1;

3.shmdt

shmdt函数的作用是将共享内存从当前进程中分离,它的参数是shmat返回的地址指针,成功时返回0,失败返回-1

4.shmctl

它的定义如下所示:

int shmctl(int shm_id,int cmd,struct shmid_ds *buf)

shmid_ds结构体至少包含以下成员

struct shmid_ds {

uid_t shm_perm.uid;

uid_t shm_perm.gid;

mode_t shm_perm.mode;

}

第一个参数shm_id是shmget返回的共享内存标识符

第二个参数command是要采取的动作,它可以取3个值

IPC_STAT    把shmid_ds结构体中的数据设置为共享内存的当前关联

IPC_SET      如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值

IPC_RMID     删除共享内存段

第三个参数buf是一个空指针,它指向包含共享内存模式和访问权限的结构

下面是一个共享内存例子:

(1)我们首先创建一个头文件,如下:

#define TEXT_SZ 2048

 

struct shared_use_st {

int written_by_you;

char some_text[TEXT_SZ];

};

(2)第一个程序shm1.c是消费者程序,如下:

#include <unistd.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

 

#include <sys/shm.h>

 

#include "shm_com.h"

int main()

{

int running = 1;

void *shared_memory = (void *)0;//指向共享内存第一个字节指针

struct shared_use_st *shared_stuff;

int shmid;//存放共享内存标识符

 

srand((unsigned int) getpid());

               //创建共享内存

shmid = shmget((key_t)1234 ,sizeof(struct shared_use_st),0666 | IPC_CREAT);

if(shmid == -1) {

fprintf(stderr,"shmget failed\n" );

exit(EXIT_FAILURE);

}

shared_memory = shmat(shmid,(void *)0,0);//把共享内存连接到进程地址空间

if(shared_memory == (void *)-1) {

fprintf(stderr,"shmat failed\n");

exit(EXIT_FAILURE);

}

printf("Memory attached at %x\n",(int)shared_memory);

shared_stuff = (struct shared_use_st *)shared_memory;

shared_stuff->written_by_you = 0;

while(running) {//打印消息

if(shared_stuff->written_by_you) {

                                               //从共享内存中读出数据

printf("You wrote: %s",shared_stuff->some_text);

sleep(rand() % 4);

shared_stuff->written_by_you = 0;

if(strncmp(shared_stuff->some_text,"end",3) ==0) {

running = 0;

}

}

}

if(shmdt(shared_memory) == -1) {//把共享内存从进程地址空间分离

fprintf(stderr,"shmdt failed\n");

exit(EXIT_FAILURE);

}

if (shmctl(shmid,IPC_RMID,0) == -1) {//删除共享内存

fprintf(stderr,"shmctl(IPC_RMID) failed\n");

exit(EXIT_FAILURE);

}

exit(EXIT_SUCCESS);

}

(3)第二个程序shm2.c是生产者,如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shm_com.h"
 
int main()
{
int running = 1;
void *shared_memory = (void *)0;
struct shared_use_st *shared_stuff;
char buffer[BUFSIZ];
int shmid;
 
shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666 | IPC_CREAT);
 
if(shmid == -1) {
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
shared_memory = shmat(shmid,(void *)0,0);
if(shared_memory == (void *)-1) {
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %x\n",(int)shared_memory);
shared_stuff = (struct shared_use_st *)shared_memory;
while(running) {
while(shared_stuff->written_by_you == 1) {
sleep(1);
printf("waiting for client...\n");
}
printf("Enter some text: ");
fgets(buffer,BUFSIZ,stdin);
                               //把数据写入共享内存
strncpy(shared_stuff->some_text,buffer,TEXT_SZ);
shared_stuff->written_by_you = 1;
 
if(strncmp(buffer,"end",3) == 0){
running = 0;
}
}
if(shmdt(shared_memory) == -1) {
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
运行这两个程序时,输出如下:
root@www:/opt/chengxu# ./shm2
Memory attached at b7808000
Enter some text: hello
You wrote: hello
waiting for client...
waiting for client...
Enter some text: Linux
You wrote: Linux
waiting for client...
waiting for client...
Enter some text: end
root@www:/opt/chengxu# You wrote: end
 
[1]+  Done                    ./shm1
root@www:/opt/chengxu# 
 
消息队列
消息队列与命名管道有许多相似之处,但少了在打开和关闭管道方面的复杂性,消息队列提供了一种在两个不相关的进程之间的传递数据的相当简单且有效的方法
消息队列函数的定义如下:
#include <sys/msg.h>
 
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
int msgget(key_t key,int msgflg);
int msgrcv(int msgid,void *msg_ptr,size_t msg_sz,long int msgtype,int msgflg);
int msgsnd(int msqid,const void *msg_ptr,size_t msg_sz,int msgflg);

1.msgget函数
我们用msgget函数来创建和访问一个消息队列
int msgget(key_t key,int msgflg);
第一个参数和以前的一样,用一个键值来命名某个特定的消息队列,第二个参数msgflg由9个权限标志组成
2.msgand函数
msgsnd函数用来把消息添加到消息队列中
int msgsnd(int msqid,const void *msg_ptr,size_t msg_sz,int msgflg);
消息受两方面的约束,首先,它的长度必须小于系统规定的上限,其次,它必须以一个长整型成员变量开始最好把消息结构定义为下面这样
struct my_messags {
long int message_type;
}
第一个参数msgid是由msgget函数返回的消息队列标识符
第二个参数msg_ptr是指向一个准备发送消息的指针,消息必须像刚才所说的那样以一个长整型成员变量开始
第三个参数msg_sz是msg_ptr指向的消息长度
第四个参数msgflg控制在当前消息队列满或队列消息到达系统范围的限制时即将要发生的事情
3.msgrcv函数
msgrcv函数从一个消息队列中获取消息
int msgrcv(int msgid,void *msg_ptr,size_t msg_sz,long int msgtype,int msgflg);
第一个参数msgid是由msgget函数返回的消息队列标识符
第二个参数msg_ptr是指向一个准备接收消息的指针,消息必须像刚才所说的那样以一个长整型成员变量开始
第三个参数msg_sz是msg_ptr指向的消息长度
第四个参数msgtype是一个长整型,它可以实现一种简单形式的接收优先级,如果为0,就表示获取队列中的第一个可用消息
第五个参数msgflg用于控制当队列中没有相应类型的消息可以接收时将发生的事情
4.msgctl函数
最后一个消息队列函数是msgctl,它的作用与共享内存的控制函数非常相似
int msgctl(int msqid,int cmd,struct msqid_ds *buf);

msgid_ds结构体至少包含以下成员

struct msgid_ds {

uid_t msg_perm.uid;

uid_t msg_perm.gid;

mode_t msg_perm.mode;

}

第一个参数msgid是msgget返回的共享内存标识符

第二个参数command是要采取的动作,它可以取3个值

IPC_STAT 把shmid_ds结构体中的数据设置为共享内存的当前关联

IPC_SET 如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值

IPC_RMID 删除共享内存段

成功时返回0,失败时返回-1

 

下面是消息队列的例子:

(1)下面是接收者程序msg1.c的代码:

 #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include <sys/msg.h>

struct my_msg_st { //准备接收数据结构体
  long int my_msg_type;
  char some_text[BUFSIZ];
};

int main()
{
  int running = 1;
  int msgid;
  struct my_msg_st some_data;
  long int msg_to_receive = 0;

  msgid = msgget((key_t)1234,0666 | IPC_CREAT);//创建消息队列
  if(msgid == -1) {
    fprintf(stderr,"msgget failed with error: %d\n",errno);
    exit(EXIT_FAILURE);
  }

  while(running) {
    if(msgrcv(msgid,(void *)&some_data,BUFSIZ,msg_to_receive,0) == -1) {//从队列中获取消息
      fprintf(stderr,"msgrcv failed with error: %d\n",errno);
      exit(EXIT_FAILURE);
    }
    printf("You wrote: %s",some_data.some_text);//打印获取消息
    if(strncmp(some_data.some_text,"end",3) == 0) {
      running = 0;
    }
  }
  if(msgctl(msgid,IPC_RMID,0) == -1) {
    fprintf(stderr,"msgctl(IPC_RMID) failed\n");
    exit(EXIT_FAILURE);
  }
  exit(EXIT_SUCCESS);
}

(2)下面是发送者程序msg2.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include <sys/msg.h>

#define MAX_TEXT 512

struct my_msg_st {
  long int my_msg_type;
  char some_text[MAX_TEXT];
};

int main()
{
  int running = 1;
  struct my_msg_st some_data;
  int msgid;
  char buffer[BUFSIZ];

  msgid = msgget((key_t)1234,0666 | IPC_CREAT);//创建或访问消息队列

  if(msgid == -1) {
    fprintf(stderr,"msgget failed with error: %d\n",errno);
    exit(EXIT_FAILURE);
  }

  while(running) {
    printf("Enter some text: ");
    fgets(buffer,BUFSIZ,stdin);
    some_data.my_msg_type = 1;
    strcpy(some_data.some_text,buffer);

    if(msgsnd(msgid,(void *)&some_data,MAX_TEXT,0) == -1) {//把数据写入队列
      fprintf(stderr,"msgsnd failed\n");
      exit(EXIT_FAILURE);
    }
    if (strncmp(buffer,"end",3) == 0) {
      running = 0;
    }
  }
  exit(EXIT_SUCCESS);
}

运行这两个程序如下所示:

root@www:/opt/chengxu# gcc msg1.c  -o msg1
root@www:/opt/chengxu# gcc msg2.c  -o msg2
root@www:/opt/chengxu# ./msg2
Enter some text: hello
Enter some text: How are you today?
Enter some text: end
root@www:/opt/chengxu# ./msg1
You wrote: hello
You wrote: How are you today?
You wrote: end
root@www:/opt/chengxu#

....

posted @ 2014-05-18 23:33  鱼时代  阅读(1092)  评论(0编辑  收藏  举报