转自ChinaUnix 作者:graylocus
原地址:http://blog.chinaunix.net/uid-23193900-id-3194924.html
1.信号量的概念
信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用。大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直至资源可用。
当进程不再使用一个信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。而在信号量的创建及初始化上,不能保证操作均为原子性。
内核为每个信号量集都设置一个shmid_ds结构,同时用一个无名结构来标识一个信号量。
表示一个信号量的结构:
struct{ unsigned short semval;
pid_t sempid;
unsigned short semncent;
.....
};
2.信号量的创建
使用函数semget创建或者获得一个信号量集ID(不是创建一个信号量,该信号量集可以包含多个信号量)
#include
int semget(key_t key,int nsems, int flag);
参数key用来变换成一个标识符,每一个IPC对象与一个key相对应。函数使用flag的相应权限位对ipc_perm结构中的mode域赋值。
nsems是一个大于等于0的值,用于指明该信号量集中可用资源数(在第一次创建一个信号量时需要指定)。当打开一个已存在的信号量集时,该参数值为0。函数执行成功,返回信号量集的标识符,失败,返回-1.
返回值:如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1:errno=EACCESS(没有权限)
EEXIST(信号量集已经存在,无法创建)
EIDRM(信号量集已经删除)
ENOENT(信号量集不存在,同时没有使用IPC_CREAT)
ENOMEM(没有足够的内存创建新的信号量集)
ENOSPC(超出限制)
系统调用semget()的第一个参数是关键字值(一般是由系统调用ftok()返回的)。系统内核将此值和系统中存在的其他的信号量集的关键字值进行比较。打开和存取操作与参数semflg中的内容相关。IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL当和 IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。
如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识符,要么返回-1。IPC_EXCL单独使用没有意义。参数nsems指出了一个新的信号量集中应该创建的信号量的个数。信号量集中最多的信号量的个数是在linux/sem.h中定义的:
3.信号量所表示的资源的操作
函数semop用以操作一个信号量集,实质是通过修改sem_op指定对资源要进行的操作,semctl函数则是对信号量本身的值进行操作,可以修改信号量的值或者删除一个信号量,这是他们二者容易混淆的地方:
int semop(int semid,struct sembuf semoparray[],size_t nops);
semid是通过semget函数返回的一个信号量集标识符ID,nops标明了参数semoparray所指向数组中的元素个数。semoparray是一个结构数组指针。结构体struct sembuf 用来说明要执行的操作。
struct sembuf{
unsigned short sem_num; //对应信号量集中的某一个资源
short sem_op; //指明所要执行的操作
short sem_flg; //函数semop的行为
}
在sembuf结构中,sem_num是相对应的信号量集中的某一个资源,所以它的值是一个从 0--相对应的信号量集的资源总数(ipc_perm.sem_nsems)之间的整数。
sem_op指明想要进行的操作,sem_flg说明函数semop的行为。sem_op的值是一个整数。
sem_op:
(1)> 0:释放相应的资源数,如果有两个信号量,释放信号量1,则其semval+1,对信号量这个无名结构体的操作,可以通过下面介绍的semctl函数实现。
(2)0:进程阻塞直到信号量的相应值为0,当信号量已经为0,函数立即返回。
(3)< 0:请求sem_op的绝对值的资源数。
sem_flg 参数:
该参数可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整值)才会更新。 此外,如果此操作指定SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行---它永远不会强制等待的过程。调用进程必须有改变信号量集的权限。
sem_flg公认的标志是 IPC_NOWAIT 和 SEM_UNDO。如果操作指定SEM_UNDO,它将会自动撤消该进程终止时。
在标准操作程序中的操作是在数组的顺序执行、原子的,那就是,该操作要么作为一个完整的单元,要么不。如果不是所有操作都可以立即执行的系统调用的行为取决于在个人sem_flg领域的IPC_NOWAIT标志的存在。
示例1:sem.c
点击(此处)折叠或打开
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int sem_id;
int nsems=1;
int flag=0666;
struct sembuf buf;
sem_id=semget(IPC_PRIVATE,nsems,flag);
if(sem_id<0)
{
printf("semget fail!\n");
exit(1);
}
printf("semaphore :%d\n",sem_id);
buf.sem_num=0;
buf.sem_op=1;
buf.sem_flg=IPC_NOWAIT;
if(semop(sem_id,&buf,nsems)<0)
{
perror("semop error");
exit(1);
}
system("ipcs -s");
exit(0);
}
./sem
------ Semaphore Arrays --------
key semid owner perms nsems
0x00000000 0 root 666 1
0x00000000 32769 root 666 1
0x00000000 65538 root 666 1
semaphore :65538
示例2:sem2.c
创建了包含两个信号量的一个信号量集
点击(此处)折叠或打开
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int sem_id;
int nsems=2;
int flag=0666;
struct sembuf buf[2];
sem_id=semget(IPC_PRIVATE,nsems,flag);
if(sem_id<0)
{
printf("semget fail!\n");
exit(1);
}
printf("semaphore :%d\n",sem_id);
buf[1].sem_num=1;
buf[1].sem_op=1;
buf[1].sem_flg=IPC_NOWAIT;
buf[0].sem_num=0;
buf[0].sem_op=1;
buf[0].sem_flg=IPC_NOWAIT;
if(semop(sem_id,buf,nsems)<0)
{
perror("semop error");
exit(1);
}
system("ipcs -s");
exit(0);
}
------ Semaphore Arrays --------
key semid owner perms nsems
0x00000000 0 root 666 1
0x00000000 32769 root 666 1
0x00000000 65538 root 666 1
0x00000000 98307 root 666 2
0x00000000 131076 root 666 2
0x00000000 163845 root 666 2
0x00000000 196614 root 666 2
0x00000000 229383 root 666 2
semaphore :229383
4.信号量集的操作
信号量集的操作函数semctl
#include
int semctl(int sem_id,int semnum, int cmd[,union semun arg]);
sem_id是信号量集标识符,semnum指定集中某一个信号灯,类似于struct sembuf 结构数组的下标,用来对指定资源进行操作。cmd定义了函数要进行的操作。
cmd的值:
GETVAL 返回结构体数组中以semnum为下标的元素的成员semval值
SETVAL 使用arg.val对该信号量的semnum.sempid赋值
GETPID GETTNCNT GETZCNT GETALL SETALL ..
arg为可选参数,根据参数cmd的相关操作来选择使用
union semun{ int val; struct semid_ds *buf; unsigned short *array;}
示例sem_ctl.c
点击(此处)折叠或打开
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int sem_id;
int nsems=2;
int flag=0666;
int val=1;
sem_id=semget(IPC_PRIVATE,nsems,flag);
if(sem_id<0)
{
printf("semget fail!\n");
exit(1);
}
printf("semaphore :%d\n",sem_id);
system("ipcs -s");
if((val=semctl(sem_id,1,GETVAL))<0)
{
printf("getval fail!\n");
}
else
{
printf("val = %d\n",val);
}
if(semctl(sem_id,0,IPC_RMID)<0)
{
perror("semctl!");
exit(1);
}
else
{
printf("semaphore removed:%d!\n",sem_id);
system("ipcs -s");
}
exit(0);
}
------ Semaphore Arrays --------
key semid owner perms nsems
0x00000000 0 root 666 1
0x00000000 32769 root 666 1
.....
0x00000000 393228 root 666 2
0x00000000 720909 root 666 2
------ Semaphore Arrays --------
key semid owner perms nsems
0x00000000 0 root 666 1
0x00000000 32769 root 666 1
......
0x00000000 131076 root 666 2
0x00000000 393228 root 666 2
semaphore :720909
val = 0 //val未初始化,尽管前面定义为1,这里还是0
semaphore removed:
以上是对信号量与信号量集的基本操作,
进程间的信号量机制的测试:sem_process.c
点击(此处)折叠或打开
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
typedef union semunion{
int val;
struct semid_ds *buf;
unsigned short *array;
} semun;
typedef semun semunion;
int sem_init(int key)
{
int semid;
semunion arg;
printf("key is %d\n",key);
semid=semget(key,1,0660|IPC_CREAT);//创建一个信号量集,且只有一个信号量,控制的资源key
if(semid<0)
{
perror("semget fail!\n");
exit(1);
}
arg.val = 2;
if(semctl(semid,0,SETVAL,arg)<0) //设置信号量的参数val
{
perror("semget fail!\n");
exit(2);
}
return semid;
}
int sem_pos(int semid)
{
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=-1; //请求-1的绝对值个资源,
buf.sem_flg=SEM_UNDO;
if(semop(semid,&buf,1)== -1)
{
perror("sem_op \n");
exit(3);
}
printf("sem_poss success!\n");
return 0;
}
int sem_rel(int semid)
{
struct sembuf buf;
buf.sem_num=0; //这个就是信号量集中的信号量元素下标
buf.sem_op=1; //释放1 个资源,
buf.sem_flg=IPC_NOWAIT;
if(semop(semid,&buf,1)== -1)
{
perror("sem_op \n");
exit(3);
}
printf("sem_rel success!\n");
return 0;
}
int sem_rmv(int semid)
{
if(semctl(semid,0,IPC_RMID)<0)
{
perror("semctl\n");
exit(3);
}
return 0;
}
int main(void)
{
key_t key;
int pid,fd,semid,n;
char str[80];
key=ftok("./test.txt",5);//获得关键字,这就是信号量实际控制的资源
semid=sem_init(key);
if(semid<0)
{
perror("semget fail!\n");
exit(1);
}
// 读写方式,如果文件不存在则新建,打开之后指针定位在文件尾部
fd=open("./test.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
while((pid=fork())==-1); //创建子进程
if(pid==0)//child process
{
sleep(1); //睡眠1s
sem_pos(semid);
lseek(fd,SEEK_SET,0); //定位文件
read(fd,str,sizeof(str)); //读文件
sem_rel(semid); //释放操作
printf("child:read str from test file:%s\n",str);
exit(0);
}
else //parent process
{
sem_pos(semid); // P操作
printf("parent:please enter a str for test file(strlen<80):\n");
gets(str); //输入
n=strlen(str); // 长度
lseek(fd, SEEK_SET,0); //定位
write(fd,str,n); //写
sem_rel(semid); //释放
wait(0);
close(fd); //关闭文件
sem_rmv(semid); //删除信号量
exit(0);
}
return(0);
}