Linux提供两种信号量:
(1) 内核信号量,由内核控制路径使用
(2) 用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEM
V信号量。
POSIX信号量又分为有名信号量和无名信号量。
有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名
信号量,其值保存在内存中。
内核信号量
内核信号量的构成
内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而,
当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源被释放时,进程才再次变为可运行。
只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内
核信号量。
内核信号量是struct semaphore类型的对象,它在
#include #include #include #include #include int number; // 被保护的全局变量sem_t sem_id;void* thread_one_fun(void *arg){sem_wait(&sem_id);printf("thread_one have the semaphore\n");number++;printf("number = %d\n",number);sem_post(&sem_id);}void* thread_two_fun(void *arg){sem_wait(&sem_id);printf("thread_two have the semaphore \n");number--;printf("number = %d\n",number);sem_post(&sem_id);}int main(int argc,char *argv[]){number = 1;pthread_t id1, id2;sem_init(&sem_id, 0, 1);pthread_create(&id1,NULL,thread_one_fun, NULL);pthread_create(&id2,NULL,thread_two_fun, NULL);pthread_join(id1,NULL);pthread_join(id2,NULL);printf("main,,,\n");return 0;}
上面的例程,到底哪个线程先申请到信号量资源,这是随机的。如果想要某个特定的顺
序的话,可以用2个信号量来实现。例如下面的例程是线程1先执行完,然后线程2才继
续执行,直至结束。
int number; // 被保护的全局变量sem_t sem_id1, sem_id2;void* thread_one_fun(void *arg){sem_wait(&sem_id1);printf(“thread_one have the semaphore\n”);number++;printf(“number = %d\n”,number);sem_post(&sem_id2);}void* thread_two_fun(void *arg){sem_wait(&sem_id2);printf(“thread_two have the semaphore \n”);number–;printf(“number = %d\n”,number);sem_post(&sem_id1);}int main(int argc,char *argv[]){number = 1;pthread_t id1, id2;sem_init(&sem_id1, 0, 1); // 空闲的sem_init(&sem_id2, 0, 0); // 忙的pthread_create(&id1,NULL,thread_one_fun, NULL);pthread_create(&id2,NULL,thread_two_fun, NULL);pthread_join(id1,NULL);pthread_join(id2,NULL);printf(“main,,,\n”);return 0;}
(b)无名信号量在相关进程间的同步
说是相关进程,是因为本程序中共有2个进程,其中一个是另外一个的子进程(由
fork
产生)的。
本来对于fork来说,子进程只继承了父进程的代码副本,mutex理应在父子进程
中是相互独立的两个变量,但由于在初始化mutex的时候,由pshared = 1指
定了mutex处于共享内存区域,所以此时mutex变成了父子进程共享的一个变
量。此时,mutex就可以用来同步相关进程了。
#include #include #include #include #include #include #include #include #include int main(int argc, char **argv){int fd, i,count=0,nloop=10,zero=0,*ptr;sem_t mutex;//open a file and map it into memoryfd = open("log.txt",O_RDWR|O_CREAT,S_IRWXU);write(fd,&zero,sizeof(int));ptr = mmap( NULL,sizeof(int),PROT_READ |PROT_WRITE,MAP_SHARED,fd,0 );close(fd);/* create, initialize semaphore */if( sem_init(&mutex,1,1) < 0) //{perror("semaphore initilization");exit(0);}if (fork() == 0){ /* child process*/for (i = 0; i < nloop; i++){sem_wait(&mutex);printf("child: %d\n", (*ptr)++);sem_post(&mutex);}exit(0);}/* back to parent process */for (i = 0; i < nloop; i++){sem_wait(&mutex);printf("parent: %d\n", (*ptr)++);sem_post(&mutex);}exit(0);}
2.有名信号量
有名信号量的特点是把信号量的值保存在文件中。
这决定了它的用途非常广:既可以用于线程,也可以用于相关进程间,甚至是不相关
进程。
(a)有名信号量能在进程间共享的原因
由于有名信号量的值是保存在文件中的,所以对于相关进程来说,子进程是继承了父
进程的文件描述符,那么子进程所继承的文件描述符所指向的文件是和父进程一样的,当
然文件里面保存的有名信号量值就共享了。
(b)有名信号量相关函数说明
有名信号量在使用的时候,和无名信号量共享sem_wait和sem_post函数。
区别是有名信号量使用sem_open代替sem_init,另外在结束的时候要像关闭文件
一样去关闭这个有名信号量。
(1)打开一个已存在的有名信号量,或创建并初始化一个有名信号量。一个单一的调用就完
成了信号量的创建、初始化和权限的设置。
sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);
name是文件的路径名;
Oflag 有O_CREAT或O_CREAT|EXCL两个取值;
mode_t控制新的信号量的访问权限;
Value指定信号量的初始化值。
注意:
这里的name不能写成/tmp/aaa.sem这样的格式,因为在linux下,sem都是创建
在/dev/shm目录下。你可以将name写成“/mysem”或“mysem”,创建出来的文件都
是“/dev/shm/sem.mysem”,千万不要写路径。也千万不要写“/tmp/mysem”之类的。
当oflag = O_CREAT时,若name指定的信号量不存在时,则会创建一个,而且后
面的mode和value参数必须有效。若name指定的信号量已存在,则直接打开该信号量,
同时忽略mode和value参数。
当oflag = O_CREAT|O_EXCL时,若name指定的信号量已存在,该函数会直接返
回error。
(2) 一旦你使用了信号量,销毁它们就变得很重要。
在做这个之前,要确定所有对这个有名信号量的引用都已经通过sem_close()函数
关闭了,然后只需在退出或是退出处理函数中调用sem_unlink()去删除系统中的信号量,
注意如果有任何的处理器或是线程引用这个信号量,sem_unlink()函数不会起到任何的作
用。
也就是说,必须是最后一个使用该信号量的进程来执行sem_unlick才有效。因为每个
信号灯有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把
name所指的信号灯从文件系统中删除。也就是要等待最后一个sem_close发生。
(c)有名信号量在无相关进程间的同步
前面已经说过,有名信号量是位于共享内存区的,那么它要保护的资源也必须是位于
共享内存区,只有这样才能被无相关的进程所共享。
在下面这个例子中,服务进程和客户进程都使用shmget和shmat来获取得一块共享内
存资源。然后利用有名信号量来对这块共享内存资源进行互斥保护。
File1: server.c#include #include #include #include #include #include #include #include #define SHMSZ 27char SEM_NAME[]= "vik";int main(){char ch;int shmid;key_t key;char *shm,*s;sem_t *mutex;//name the shared memory segmentkey = 1000;//create & initialize semaphoremutex = sem_open(SEM_NAME,O_CREAT,0644,1);if(mutex == SEM_FAILED){perror("unable to create semaphore");sem_unlink(SEM_NAME);exit(-1);}//create the shared memory segment with this keyshmid = shmget(key,SHMSZ,IPC_CREAT|0666);if(shmid<0){perror("failure in shmget");exit(-1);}//attach this segment to virtual memoryshm = shmat(shmid,NULL,0);//start writing into memorys = shm;for(ch='A';ch<='Z';ch++){sem_wait(mutex);*s++ = ch;sem_post(mutex);}//the below loop could be replaced by binary semaphorewhile(*shm != '*'){sleep(1);}sem_close(mutex);sem_unlink(SEM_NAME);shmctl(shmid, IPC_RMID, 0);exit(0);}File 2: client.c#include #include #include #include #include #include #include #include #define SHMSZ 27char SEM_NAME[]= "vik";int main(){char ch;int shmid;key_t key;char *shm,*s;sem_t *mutex;//name the shared memory segmentkey = 1000;//create & initialize existing semaphoremutex = sem_open(SEM_NAME,0,0644,0);if(mutex == SEM_FAILED){perror("reader:unable to execute semaphore");sem_close(mutex);exit(-1);}//create the shared memory segment with this keyshmid = shmget(key,SHMSZ,0666);if(shmid<0){perror("reader:failure in shmget");exit(-1);}//attach this segment to virtual memoryshm = shmat(shmid,NULL,0);//start readings = shm;for(s=shm;*s!=NULL;s++){sem_wait(mutex);putchar(*s);sem_post(mutex);}//once done signal exiting of reader:This can be replaced byanother semaphore*shm = '*';sem_close(mutex);shmctl(shmid, IPC_RMID, 0);exit(0);}
SYSTEM V信号量
这是信号量值的集合,而不是单个信号量。相关的信号量操作函数由
#include #include #include #include static int nsems;static int semflg;static int semid;int errno=0;union semun {int val;struct semid_ds *buf;unsigned short *array;}arg;int main(){struct sembuf sops[2]; //要用到两个信号量,所以要定义两个操作数组int rslt;unsigned short argarray[80];arg.array = argarray;semid = semget(IPC_PRIVATE, 2, 0666);if(semid < 0 ){printf("semget failed. errno: %d\n", errno);exit(0);}//获取0th信号量的原始值rslt = semctl(semid, 0, GETVAL);printf("val = %d\n",rslt);//初始化0th信号量,然后再读取,检查初始化有没有成功arg.val = 1; // 同一时间只允许一个占有者semctl(semid, 0, SETVAL, arg);rslt = semctl(semid, 0, GETVAL);printf("val = %d\n",rslt);sops[0].sem_num = 0;sops[0].sem_op = -1;sops[0].sem_flg = 0;sops[1].sem_num = 1;sops[1].sem_op = 1;sops[1].sem_flg = 0;rslt=semop(semid, sops, 1); //申请0th信号量,尝试锁定if (rslt < 0 ){printf("semop failed. errno: %d\n", errno);exit(0);}//可以在这里对资源进行锁定sops[0].sem_op = 1;semop(semid, sops, 1); //释放0th信号量rslt = semctl(semid, 0, GETVAL);printf("val = %d\n",rslt);rslt=semctl(semid, 0, GETALL, arg);if (rslt < 0){printf("semctl failed. errno: %d\n", errno);exit(0);}printf("val1:%d val2: %d\n",(unsigned int)argarray[0],(unsigned int)argarray[1]);if(semctl(semid, 1, IPC_RMID) == -1){Perror(“semctl failure while clearing reason”);}return(0);}
swoole教学