所谓信号量,其实就是一个数字。内核给这个数字赋予一定的含义,让它等于不同的值时所表示的意义不同。这样就可以用它来标示某种资源是否正被使用。信号的分类其实挺多的,主要还是二值和计数器。这里讨论二值
现在有个文件,有两个进程要同时访问它。进程A 要往里面写入 "Math class is cancel",进程B 要往里面写入“English test”。正常情况下这两个信息会被完整的写入文件中。但是如果进程A写到"Math class" 就暂停,接着B进程就开始写“English test”,最后进程A继续写剩下的“is cancel ”,那文件最后的结果就是“Math class English test is cancel”。结果显然错的离谱。究其错误的原因其实很简单,两个进程在同时公用一个资源但是又没有一种机制来控制访问的先后顺序。因为Linux 是一个多任务的系统,这中资源被多个任务同时访问的情况是非常普遍的。我们需要一种机制来控制资源能够被有序的访问。信号量就是一种实现。
信号量互斥的实现机制简单描述如下:
让两个进程在真正写入数据之前先测试信号量sig。当进程A要写入文件时,它先测试信号量sig,如果信号量sig 等于1,表示这个文件没有其他进程再使用,那么它就将这个信号量减1 让它等于0,接着直接写数据到这个文件。但是当进程B测试信号量sig 时发现它等于0 ,表示有进程在使用它,那么内核就让进程B挂起,当信号量的值被置为1时它才能继续执行写入操作。、
描述完信号量的机制就可以讨论Linux内核对于信号量的实现。
事实上linux内核关于信号量的接口还是很简洁的。就三个函数:
int semget(key_t key, int num_sems, int sem_flags);
int semop(int semid, struct sembuf *sops, size_t ops_num);
int semctl(int semid, int semnum, int command, ...);
semget函数
这个函数的功能是 创建/打开一个信号量集合,所谓集合其实就相当于数组。一个进程可能与多种资源相关联,可以用多个信号量来控制这些资源的访问。可见Linux 内核提供的信号量集合的概念是合理的。下面讨论它的参数和返回值:
返回值很简单,就是一个整型数----信号量标识符,标识符是内核为每个打开了的信号量集合而分配的,访问某个信号量集合就得靠它了(类比文件编程里的文件描述符)。key参数:键值。内核为所有的信号量集合维护了不同的键值,不管信号量集合是否被打开,这个键值都存在。键值其实就相当于文件名,但是键值是个整数,它可以*指定,只是*指定容易使得不同的信号量拥有相同的键值。因此内核提供了一个函数来分配键值:key_t ftok(const char *pathname, int proj_num);这个函数合成键值的机制简单描述下:
根据文件名pathname在内核中表示的数字以及proj_num共同合成一个键值key 并返回给调用者。num_sems参数:表明这个信号量集合的信号量个数。sem_flags参数:打开的标志,可以取值IPC_CREAT。当函数调用中有这个参数,并且键值指定的信号量集合不存在,那么这个函数就创建一个key 表示的信号量集合。
semop 函数
semop 函数用来信号量集合。事实上信号量集合的操作很简单,无非就是将信号量加一或者减一,来分别表示释放或者获取信号量。返回值除了表示操作的成败,没有什么意义。semid 参数:打开的信号量集合的标识符,从semget 返回。ops_num 参数:表示这个操作函数操作多少个信号量,因为信号量集合可能有多个信号量。 ops执行的结构体定义如下:
struct sembuf
{
unsigned short sem_num;//表明这个信号量在信号量集合中是哪一个
short sem_op; //表明操作类型 整数表明+1, 负数表明-1。事实上获取信号量和释放 信号量就是在这里区别
short sem_flg;
}
semctl 函数
semctl函数是在信号量上执行的多种操作。semid参数:表明要操作的信号量集合的标识符。semnum参数:表明要操作的信号量个数。command参数:表明要执行的命令。当我们要给信号量赋初值时可以令command == SETVAL,然后在省略参数给出要赋的值。
函数说明完了,下面给出一个简单的实例测试一下信号量互斥编程:
进程A:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/sem.h> int main() { int fd = 0; key_t key; int semid; struct sembuf sops; //创建并打开信号量 key = ftok("/home",1); semid = semget(key,1,IPC_CREAT); semctl(semid,0,SETVAL,1); /* 0.打开公示栏*/ fd = open("./board.txt",O_RDWR|O_APPEND); //获取信号量 sops.sem_num = 0; sops.sem_op = -1; semop(semid,&sops,1); /* 1. 写入“数学课” */ write(fd,"Math class",11); /* 2. 暂停 */ sleep(10); /* 3. 写入“取消”*/ write(fd,"is cancel",10); //释放信号量 sops.sem_num = 0; sops.sem_op = 1; semop(semid,&sops,1); /* 4. 关闭公示栏 */ close(fd); return 0; }
进程B:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/sem.h> int main() { int fd = 0; key_t key; int semid; struct sembuf sops; /* 0.打开公示栏 */ fd = open("./board.txt",O_RDWR|O_APPEND); //打开信号量 key = ftok("/home",1); semid = semget(key,1,IPC_CREAT); //获取信号量 sops.sem_num = 0; sops.sem_op = -1; semop(semid,&sops,1); /* 1. 写入“英语课” */ write(fd,"Enclish exam",13); //释放信号量 sops.sem_num = 0; sops.sem_op = 1; semop(semid,&sops,1); /* 2. 关闭公示栏 */ close(fd); return 0; }