信号量就是具有原子性的计数器,就相当于一把锁,在每个进程要访问临界资源时,必须要向信号量拿个锁”,它才能进去临界资源这个“房间”,并锁上门,不让其他进程进来,此时信号量执行P()操作,锁的数目减少了一个,所以计数器减1,;当它访问完成时,它出来,将锁还给信号量,执行V()操作,计数器加1;
该函数用来创建一个新信号量,其定义为:int semget(key_t key, int num, int sem_flags) Key是整数值,程序对所有信号量的访问都是间接的,先提供一个键,再由系统生成一个信号量标识符。num_sem参数指定需要的信号量数目,一般取1;sem_flags参数是一组标志。 semget函数在成功时返回一个正数,也就是其他信号量函数用到的信号量标识符,失败时返回-1.
定义为:int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops) sem_id 表示信号量标识符,semops指向一个结构数组的指针,每个数组元素至少包含以下几个成员: struct sembuf { short sem_num; //信号量编号,一般取0 short sem_op;//信号量需要改变的值,-1/+1 short sem_flg;//设置为SEM_UNDO }
定义为:int semctl(int sem_id, int sem_num, int command,…) sem_id:表示信号量标识符,sem_num表示信号量编号一般取0,command参数是将要采取的行动,如:SETVAL:用来把信号量初始化为一个已知的值,作用就是在信号量第一次使用前对它进行设置。IPC_RMID用于删除一个不再继续使用的信号量标识符。如果还有第四个参数,它是一个union semun结构(该联合结构可能需自己定义,可通过查阅semctl的手册查看是否给出了该定义) union semun{ int val; struct semid_ds *buf; unsigned short *array; }
让两个进程分别向显示器(linux下一切皆文件,临界资源)打印AA和BB,当没有信号量进行保护时,会出现数据混乱,例如:“AABBABAAAB…”,为了解决这一问题,我们创建信号量进行保护。打印“AA”或“BB”
vi sem.c #include<stdlib.h> #include<stdio.h> #include<unistd.h> #include<sys/sem.h> union semun{ int val; struct semid_ds *buf; unsigned short *array; //struct seminfo *buff; }; 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); /* 如果程序第一个被调用,也就是调用时含有一个参数,使得argc>1,此时就调用set_semvalue初始化信号量,并将op_char设置为x*/ if (argc > 1){ if(!set_semvalue()){ fprintf(stderr, "Failed to initialize semaphore\n"); exit(EXIT_FAILURE); } op_char = 'X'; sleep(2); } /*进入和离开临界区10次,每次循环开始的时候首先调用semaphore_p函数,它在程序将进入临界区域时设置信号量以等待进入*/ 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); /*进入临界区域后,调用semaphore_v将信号量设置为可用,然后等待一段随机的时间,再进入下一次循环*/ 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); } /*该函数用来将semctl调用的command参数设置为SETVAL来初始化信号量*/ 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); } /*通过调用semctl调用的command设置为IPC_RMID来删除信号量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"); } /*对信号量执行减1操作*/ 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); } /*对信号量执行加1操作*/ 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); }编译运行
与上述代码几乎相同,只不过变为了父子进程间的信号量机制。
vi sem2.c #include<stdlib.h> #include<stdio.h> #include<unistd.h> #include<sys/sem.h> #include<sys/ipc.h> #include<sys/types.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; union semun{ int val; struct semid_ds *buf; unsigned short *array; //struct seminfo *buff; }; 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); int id = fork(); if(id<0) { perror("fork failed\n"); return -1; } else if (id>0){ if(!set_semvalue()){ 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); 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); pause_time = rand() % 2; sleep(pause_time); } printf("\n%d - finished\n", getpid()); if (id> 0) { 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"); } 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); } 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); }编译运行结果: 注意:在这里我们可以看到子进程的pid为父进程的pid+1,注意的是fork创建子进程的返回值为0,和这里的pid是不同的。
参考:Linux程序设计第四版