信号量信号量分有名和无名信号量。它们的区别和管道及命名管道的区别类似。有名信号量要求创建一个文件,而无名信号量则直接保存在内存中。一,Posix信号量Po***信号量接口总结(见下图):上面一行是有名信号量,可于fifo相类比,其值保存在文件中,可用于进程和线程同步;下面一行是无名信号量,可与pipe相类比,其值保存在内存中,可用于进程和线程同步;中间部分,是两者的公用接口。sem_open() sem_close(),sem_unlink() //有名信号量 / |sem_wait(),sem_post() |/ / |sem_trywait(),sem_getvalue()|/sem_destroy() //无名信号量sem_init()1.公共接口1.1 接口函数说明#includeint sem_wait(sem_t *sem); 测试所指定信号量的值,它的操作是原子的。 若sem>0,那么它减1并立即返回。 若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。 int sem_trywait(sem_t *sem); 其他的行为和sem_wait一样,除了: 若sem==0,不是睡眠,而是返回一个错误EAGAIN。 int sem_post(sem_t *sem); 把指定的信号量sem的值加1; 呼醒正在等待该信号量的任意线程。 int sem_getvalue(sem_t *sem, int *sval); 取回信号量sem的当前值,把该值保存到sval中。 若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值: 1) 返回0 2) 返回阻塞在该信号量上的进程或线程数目 linux采用返回的第一种策略。注意:在这些函数中,只有sem_post是信号安全的函数,它是可重入函数。1.2 接口使用的一般流程sem_init(&sem);sem_wait(&sem);critical area;sem_post(&sem);remainder area2.无名信号量 无名信号量是保存在变量类型为sem_t的内存中。int sem_init(sem_t *sem, int pshared, unsigned int value); 1)pshared==0 用于同一多线程的同步; 2)若pshared>0 用于多个进程间的同步,此时sem必须放在共享内存中。int sem_destroy(sem_t *sem); 只能销毁由sem_init初始化的信号量,否则后果不可预料也。例1: 多线程使用信号量的简单例子:/* * simple_sem_app.c */#include "all.h"/* 每个字符输出的间隔时间 */#define TEN_MILLION 5000000L#define BUFSIZE 1024void *threadout(void *args);int main(int argc, char *argv[]){ int error; int i; int n; sem_t semlock; pthread_t *tids; if (argc != 2) { fprintf (stderr, "Usage: %s numthreads/n", argv[0]); return 1; } n = atoi(argv[1]); tids = (pthread_t *)calloc(n, sizeof(pthread_t)); if (tids == NULL) { perror("Failed to allocate memory for thread IDs"); return 1; } if (sem_init(&semlock, 0, 1) == -1) { perror("Failed to initialize semaphore"); return 1; } for (i = 0; i if (error = pthread_create(tids + i, NULL, threadout, &semlock)) { fprintf(stderr, "Failed to create thread:%s/n", strerror(error)); return 1; } } for (i = 0; i if (error = pthread_join(tids[i], NULL)) { fprintf(stderr, "Failed to join thread:%s/n", strerror(error)); return 1; } } return 0;}void *threadout(void *args){ char buffer[BUFSIZE]; char *c; sem_t *semlockp; struct timespec sleeptime; semlockp = (sem_t *)args; sleeptime.tv_sec = 0; sleeptime.tv_nsec = TEN_MILLION; snprintf(buffer, BUFSIZE, "This is thread from process %ld/n", (long)getpid()); c = buffer; /****************** entry section *******************************/ while (sem_wait(semlockp) == -1) if(errno != EINTR) { fprintf(stderr, "Thread failed to lock semaphore/n"); return NULL; } /****************** start of critical section *******************/ while (*c != '/0') { fputc(*c, stderr); c++; nanosleep(&sleeptime, NULL); } /****************** exit section ********************************/ if (sem_post(semlockp) == -1) fprintf(stderr, "Thread failed to unlock semaphore/n"); /****************** remainder section ***************************/ return NULL;}说明:该例子来自于usp。 可以把sem_wait和sme_post调用去掉,看看效果,可以看到出现了交叉输出的情况。 nanosleep调用只是为了让输出的效果更明显,没有其他意义。更多的例子见mypxsem/prodcons2-4.c3. 有名信号量有名信号量是把信号量的值保存在文件中,所以它可以用于线程也可以用于进程间的同步。如下面的形式:sem_t *mutex;...mutex = sem_open(pathname, O_CREAT | O_EXCL, FILE_MODE, 0); if ((childpid = fork()) == 0) { /* child */ ... sem_wait(mutext); ...}/* parent */...sem_post(mutex);...3.1 常用函数说明sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); 返回一个sem_t类型的指针。该指针随后可用作sem_close等的参数。 该函数参数的详细信息,可以参考手册。int sem_close(sem_t *sem); 关闭sem信号量,并释放资源。 int sem_unlink(const char *name); 在所有进程关闭信号量后删除name的信号量3.2 有名信号量的使用例子:/* * chainname.c */ #include "my_unpipc.h"#define BUFSIZE 1024#define PERMS (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)#define FLAGS (O_CREAT | O_EXCL)static int getnamed(char *name, sem_t **sem, int val);int main (int argc, char *argv[]) { char buffer[BUFSIZE]; char *c; pid_t childpid = 0; int delay; volatile int dummy = 0; int i, n; sem_t *semlockp; if (argc != 4){ /* check for valid number of command-line arguments */ fprintf (stderr, "Usage: %s processes delay semaphorename/n", argv[0]); return 1; } n = atoi(argv[1]); delay = atoi(argv[2]); for (i = 1; i if ((childpid = fork()) > 0) /* father break */ break; snprintf(buffer, BUFSIZE, "i:%d process ID:%ld parent ID:%ld child ID:%ld/n", i, (long)getpid(), (long)getppid(), (long)childpid); c = buffer; if (getnamed(argv[3], &semlockp, 1) == -1) { perror("Failed to create named semaphore"); return 1; } while (sem_wait(semlockp) == -1) /* entry section */ if (errno != EINTR) { perror("Failed to lock semlock"); return 1; } while (*c != '/0') { /* critical section */ fputc(*c, stderr); c++; for (i = 0; i dummy++; } if (sem_post(semlockp) == -1) { /* exit section */ perror("Failed to unlock semlock"); return 1; } if (wait(NULL) == -1) /* remainder section */ return 1; return 0;}static int getnamed(char *name, sem_t **sem, int val){ while (((*sem = sem_open(name, FLAGS , PERMS, val)) == SEM_FAILED) && (errno == EINTR)) ; if (*sem != SEM_FAILED) return 0; if (errno != EEXIST) return -1; while (((*sem = sem_open(name, 0)) == SEM_FAILED) && (errno == EINTR)) ; if (*sem != SEM_FAILED) return 0; return -1;}以上代码创建了一个进程链,若把sem_wait和sem_post调用去掉,可以看到输出很混乱。这是由于每个子进程都共享了父进程的文件表项,而且都指向打开的文件表项。system v 信号量===============1, 该类信号量,与posix信号量不同。它表示的信号量集,而不是单个信号量。可用于不同进程间的同步。内核为每个信号量集,维护一个如下的信息结构:struct semid_ds { struct ipc_perm sem_perm; /* 信号量集的操作许可权限 */ struct sem *sem_base; /* 某个信号量sem结构数组的指针, 当前信号量集中的每个信号量对应其中一个数组元素 */ ushort sem_nsems; /* sem_base 数组的个数 */ time_t sem_otime; /* 最后一次成功修改信号量数组的时间 */ time_t sem_ctime; /* 成功创建时间 */};struct sem { ushort semval; /* 信号量的当前值 */ short sempid; /* 最后一次返回该信号量的进程ID号 */ ushort semncnt; /* 等待semval大于当前值的进程个数 */ ushort semzcnt; /* 等待semval变成0的进程个数 */};2, 信号量操作函数a. 创建和打开信号量int semget(key_t key, int nsems, int oflag)(1) nsems>0 : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更改。(2) nsems==0 : 访问一个已存在的集合(3) 返回的是一个称为信号量标识符的整数,semop和semctl函数将使用它。(4) 创建成功后一下结构被设置: .sem_perm 的uid和gid成员被设置成的调用进程的有效用户ID和有效组ID .oflag 参数中的读写权限位存入sem_perm.mode .sem_otime 被置为0,sem_ctime被设置为当前时间 .sem_nsems 被置为nsems参数的值 .而于该集合中的每个信号量不初始化,这些结构是在semctl,用参数SET_VAL,SETALL初始化的。b. 设置信号量的值int semop(int semid, struct sembuf *opsptr, size_t nops);(1) semid 是semget返回的semid(2) nops : 是数组opsptr的个数(3) opsptr : 是操作结构的数组struct sembuf { short sem_num; /* 信号量的数目: 0,1,...,nsems-1 */ short sem_op; /* 信号量操作 */ short sem_flg; /* 操作表示符 */};(4) 若sem_op 是正数,其值就加到semval上; 若sem_op 是0,那么调用者希望等到semval变为0,如果semval是0就反回; 若sem_op 是负数,那么调用者希望等待semval变为大于或等于sem_op的绝对值.(5) sem_flg SEM_UNDO 由进程自动释放信号量 IPC_NOWAIT 不阻塞c. 对信号量集实行控制操作int semctl(int semid, int semnum, int cmd, ../* union semun arg */);其中semid是信号量集合,semnum是信号在集合中的序号,union semun{ int val; /* cmd == SETVAL */ struct semid_ds *buf /* cmd == IPC_SET或者 cmd == IPC_STAT */ ushort *array; /* cmd == SETALL, 或 cmd = GETALL */};cmd是控制命令,参数可选cmd取值如下:GETVAL, SETVAL : semid集合中semnum信号量当前的semval值GETALL,SETALL :semid集合中所有信号量的值。IPC_RMID:删除semid信号量集GETPID:返回最后成功操作该信号的进程号。IPC_STAT:返回semid集合中的struct semid_ds结构。例子:/* my_sem.c */ #include #include #include #include #include #include #include int main (int argc, char **argv) { key_t ipckey; int semid; /*建立两个信号灯结构*/ struct sembuf sem[2]; /* sembuf defined in sys/sem.h */ /* 创建IPC Key */ ipckey = ftok("/tmp/rich", 42); /* 创建信号量. 4 == READ, 2 == ALTER */ semid = semget(ipckey, 1, 0666 | IPC_CREAT); if (semid { printf("Error - %sn", strerror(errno)); _exit(1); } /*设置*/ /* These never change so leave them outside the loop */ sem[0].sem_num = 0; sem[1].sem_num = 0; sem[0].sem_flg = SEM_UNDO; /* Release semaphore on exit */ sem[1].sem_flg = SEM_UNDO; /* Release semaphore on exit */ while(1) { printf("[%s] Waiting for the semaphore to be releasedn/n", argv[1]); /* 设置两个信号灯,灯1等待,灯2请求资源锁 */ sem[0].sem_op = 0; /* Wait for zero */ sem[1].sem_op = 1; /* Add 1 to lock it*/ /*设置信号量集,两个信号量*/ semop(semid, sem, 2); /*资源锁区*/ printf("[%s] I have the semaphoren/n", argv[1]); sleep(rand() % 3); /* Critical section, sleep for 0-2 seconds */ sem[0].sem_op = -1; /* Decrement to unlock */ /*出锁,对信号量1操作*/ semop(semid, sem, 1); printf("[%s] Released semaphoren/n", argv[1]); sleep(rand() % 3); /* Sleep 0-2 seconds */ } } 10-23 07:24