以下就进入信号量的解说。
一、什么是信号量
为了防止出现因多个程序同一时候訪问一个共享资源而引发的一系列问题。我们须要一种方法。它能够通过生成并使用令牌来授权。在任一时刻仅仅能有一个运行线程訪问代码的临界区域。临界区域是指运行数据更新的代码须要独占式地运行。
而信号量就能够提供这种一种訪问机制。让一个临界区同一时间仅仅有一个线程在訪问它,也就是说信号量是用来调协进程对共享资源的訪问的。
信号量是一个特殊的变量,程序对其訪问都是原子操作。且仅仅同意对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是仅仅能取0和1的变量,这也是信号量最常见的一种形式。叫做二进制信号量。而能够取多个正整数的信号量被称为通用信号量。
这里主要讨论二进制信号量。
二、信号量的工作原理
因为信号量仅仅能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这种:
P操作 负责把当前进程由执行状态转换为堵塞状态,直到另外一个进程唤醒它。
操作为:申请一个空暇资源(把信号量减1)。若成功,则退出;若失败,则该进程被堵塞;
V操作 负责把一个被堵塞的进程唤醒。它有一个參数表。存放着等待被唤醒的进程信息。
操作为:释放一个被占用的资源(把信号量加1),假设发现有被堵塞的进程,则选择一个唤醒之。
补充:查看共享信息的内存的命令是ipcs [-m|-s|-q] (所有的话是ipcs -a) 。查看共享信息的内存的命令是ipcs [-m|-s|-q]。
举个样例,就是两个进程共享信号量sv。一旦当中一个进程运行了P(sv)操作,它将得到信号量,并能够进入临界区。使sv减1。而第二个进程将被阻止进入临界区,由于当它试图运行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并运行V(sv)释放信号量。这时第二个进程就能够恢复运行。
三、Linux的信号量机制
Linux提供了一组精心设计的信号量接口来对信号进行操作,它们不仅仅是针对二进制信号量,以下将会对这些函数进行介绍,但请注意,这些函数都是用来对成组的信号量值进行操作的。
它们声明在头文件sys/sem.h中。
1、semget函数
它的作用是创建一个新信号量或取得一个已有信号量,原型为:
int semget(key_t key, int num_sems, int sem_flags);
第一个參数key是整数值(唯一非零),不相关的进程能够通过它訪问一个信号量,它代表程序可能要使用的某个资源,程序对全部信号量的訪问都是间接的。程序先通过调用semget函数并提供一个键,再由系统生成一个对应的信号标识符(semget函数的返回值),仅仅有semget函数才直接使用信号量键,全部其它的信号量函数使用由semget函数返回的信号量标识符。
假设多个程序使用同样的key值,key将负责协调工作。
第二个參数num_sems指定须要的信号量数目。它的值差点儿总是1。
第三个參数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量。能够和值IPC_CREAT做按位或操作。
设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则能够创建一个新的,唯一的信号量,假设信号量已存在,返回一个错误。
semget函数成功返回一个对应信号标识符(非零)。失败返回-1.
2、semop函数
它的作用是改变信号量的值,原型为:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
sem_id是由semget返回的信号量标识符,sembuf结构的定义例如以下:
struct sembuf{ short sem_num;//除非使用一组信号量,否则它为0 short sem_op;//信号量在一次操作中须要改变的数据。一般是两个数。一个是-1,即P(等待)操作。 //一个是+1。即V(发送信号)操作。 short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号, //并在进程没有释放该信号量而终止时,操作系统释放信号量 };
3、semctl函数
该函数用来直接控制信号量信息,它的原型为:
int semctl(int sem_id, int sem_num, int command, ...);
假设有第四个參数,它一般是一个union semum结构,定义例如以下:
union semun{ int val; struct semid_ds *buf; unsigned short *arry; };
前两个參数与前面一个函数中的一样,command一般是以下两个值中的当中一个
SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置。其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
四、进程使用信号量通信
以下使用一个样例来说明进程间怎样使用信号量来进行通信,这个样例是两个同样的程序同一时候向屏幕输出数据,我们能够看到怎样使用信号量来使两个进程协调工作,使同一时间仅仅有一个进程能够向屏幕输出数据。注意,假设程序是第一次被调用(为了区分,第一次调用程序时带一个要输出到屏幕中的字符作为一个參数),则须要调用set_semvalue函数初始化信号并将message字符设置为传递给程序的參数的第一个字符,同一时候第一个启动的进程还负责信号量的删除工作。
假设不删除信号量,它将继续在系统中存在。即使程序已经退出,它可能在你下次执行此程序时引发问题,并且信号量是一种有限的资源。
在main函数中调用semget来创建一个信号量,该函数将返回一个信号量标识符,保存于全局变量sem_id中。然后以后的函数就使用这个标识符来訪问信号量。
建立一个项目測试一下:
semun.h
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) /* union semun is defined by including <sys/sem.h> */ #else /* according to X/OPEN we have to define it ourselves */ union semun { int val; /* value for SETVAL */ struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */ unsigned short int *array; /* array for GETALL, SETALL */ struct seminfo *__buf; /* buffer for IPC_INFO */ }; #endif
MySem.C source code:
/* After the #includes, the function prototypes and the global variable, we come to the main function. There the semaphore is created with a call to semget, which returns the semaphore ID. If the program is the first to be called (i.e. it's called with a parameter and argc > 1), a call is made to set_semvalue to initialize the semaphore and op_char is set to X. */ #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/sem.h> #include "semun.h" 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); if (argc > 1) { if (!set_semvalue()) { fprintf(stderr, "Failed to initialize semaphore\n"); exit(EXIT_FAILURE); } op_char = 'X'; sleep(2); } /* Then we have a loop which enters and leaves the critical section ten times. There, we first make a call to semaphore_p which sets the semaphore to wait, as this program is about to enter the critical section. */ for(i = 0; i < 10; i++) { if (!semaphore_p()) exit(EXIT_FAILURE); printf("%c", op_char);fflush(stdout); pause_time = rand() % 3; sleep(pause_time); printf("%c", op_char);fflush(stdout); /* After the critical section, we call semaphore_v, setting the semaphore available, before going through the for loop again after a random wait. After the loop, the call to del_semvalue is made to clean up the code. */ if (!semaphore_v()) exit(EXIT_FAILURE); pause_time = rand() % 2; sleep(pause_time); } printf("\n%d - finished\n", getpid()); if (argc > 1) { sleep(10); del_semvalue(); } exit(EXIT_SUCCESS); } /* The function set_semvalue initializes the semaphore using the SETVAL command in a semctl call. We need to do this before we can use the semaphore. */ 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); } /* The del_semvalue function has almost the same form, except the call to semctl uses the command IPC_RMID to remove the semaphore's ID. */ 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"); } /* semaphore_p changes the semaphore by -1 (waiting). */ static int semaphore_p(void){ struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = -1; /* P() */ sem_b.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_p failed\n"); return(0); } return(1); } /* semaphore_v is similar except for setting the sem_op part of the sembuf structure to 1, so that the semaphore becomes available. */ static int semaphore_v(void) { struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = 1; /* V() */ sem_b.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_v failed\n"); return(0); } return(1); }
同一时候两次执行该程序,编译后输出:
XX00XXOOXX00XXOOXX00XXOO……
XX00XXOOXX00XXOOXX00XXOO……