************************************************************************************************** posix 信号量 信号量是一种是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。 本书讨论3中类型的信号量。 posix 有名信号量:使用posix IPC名字标识,可用于进程或者线程间的同步。 posix基于内存的信号量:存放在共享内存中,可用于进程或者线程间的同步。 system v信号量:在内核中维护,可用于进程或者线程间的同步。 函数sem_open创建一个新的有名信号量或者打开一个已存在的有名信号量。有名信号量总是 既可以用于线程间同步,又可以用于进程间同步。 #include<semaphore.h> sem_t *sem_open (const char *name,int flag,.../*mode_t mode,unsigned int value*/); 返回:若成功则为指向信号量的指针,若出错则为SEM_FAILED oflag参数可以是0,O_CREAT或O_CREAT|O_EXCL,如果指定了O_CREAT标志,那么第三个和第四个参数是需要的:其中 mode参数指定权限位,value参数指定信号量的初始值。该初始值不能超过ZSEM_VALUE_MAX(这个常值必须至少为32767)。 二值信号量的初始值通常为1,计数信号量的初始值则往往大于1. 若果指定了O_CREAT(而没有O_EXCL,那么只有当所需的信号量尚未存在时才初始化它。不过所需信号量已存在的条件下指定 O_EXCL并不是一个错误。该标志的意思仅仅是如果所需的信号量尚未存在,那就创建并初始化它。但是所需信号量已存在的 条件下指定O_CREAT|O_EXCL确是一个错误。 sem_open的返回值是指向某个sem_t数据类型的指针。该指针随后sem_close,sem_wait,sem_trywait,sem_post,以及sem_getvalue 的参数。 使用sem_open打开有名信号量,使用sem_close将其关闭。 #include<semaphore.h> int sem_close(sem_t *sem_t); 返回:若成功则为0,若出错则为-1; 一个进程终止时,内核还对其上仍然打开着所有有名信号量自动执行这样的信号量关闭操作。不论该进程是自愿终止(通过 调用exit或_exit)还是非自愿的终止的,这种自动关闭都会发生。 关闭一个信号量并没有将他从系统中删除。这就是说posix有名信号量至少是随内核持续的:即使当前没有进程打开着某个 信号量,他的值仍然保持。 有名信号量使用sem_unlink从系统中删除。 #include<semaphore.h> #sem_unlink(const char *name); 每个信号量都有一个计数器记录当前的打开次数(就像文件一样),sem_unlink类似于文件I/O的unlink函数:当引用计数 还是大于0时,name就能从文件系统中删除,然而其信号量的析构(不同于将他的名字从文件系统中删除)却要等到最后一个 sem_close发生为止。 sem_wait和sem_trywait函数 sem_wait函数测试所指定的信号量的值,如果该值大于0,那么就将他减1并立即返回。如果该值等于0,调用线程就被投入 睡眠中,直到该值变为大于0,这时再将它减1,函数随后返回。 #include<semaphore.h> int sem_wait(sem_t *sem) int sem_trywait(sem_t *sem) 均返回:若成功则为0,若出错则为-1; sem_wait和sen_trywait的差别在于:当指定信号量的值已经是0时,后者并不将调用线程投入睡眠,相反,他返回一个错误; sem_post和sem_getvalue函数 当一个线程使用完成某个信号量时,他应该调用sem_post。本函数把所指定信号量值加1,然后唤醒正在等待该信号量值变为 正数的任意线程。 #include<semaphore.h> int sem_post(sem_t *sem); int sem_getvalue(sem_t *sem,int *valp); 均成功:若成功则为0,若出错则为-1 sem_getvalue在由valp指向的整数中返回所指定信号量的当前值。如果该信号量当前值以上锁,那么返回值或0,或为某个负数 ,其绝对值就是等待该信号量解锁的线程数。 前面的内容处理的是posix有名信号量。这些信号量是由一个name参数标识的,他通常指代系统文件中的某个文件。然而posix 也提供基于内存的信号量,它们由应用程序分配信号量的内存空间(也就是分配一个sem_t数据类型的内存空间),然后由系统 初始化它们的值。 #include<semaphore.h> int sem_init(sem_t *sem,int shared,unsigned int value); 返回:若出错则为-1; int sem_destory(sem_t *sem); 返回:若成功则为0,若出错则为-1; 基于内存的信号量是由sem_init初始化的。sem参数指向该应用程序必须分配的sem_t变量。如果shared为0,那么待初始化的 信号量是在同一进程中个线程间共享的,否则该信号量是在进程间共享的。当shared为非0时,该信号量必须放在某种类型 的共享内存中,而即将使用他的所有进程要能访问共享内存区。跟sem_open一样,value参数是该信号量的初始值。 使用完一个基于内存的信号量后,我们调用sem_destory摧毁它。 sem_open不需要类似于shared的参数,是因为有名信号总是可以在不同的进程间共享的。注意基于内存的信号量不使用 任何类似于O_CREAT标志的东西,也就是说,sem_init总是初始化信号量的值。因此,对于一个给定的信号量,我们必须 小心保证只调用sem_init一次。对于一个只初始化过的信号量调用sem_init其结果是未定义的。 你的确保理解sem_open和sem_init之间的基本差异。前者返回一个指向某个sem_t变量的指针,该变量由sem_open函数本身分配 并初始化。后者的第一个参数指向某个sem_t变量的指针,该变量由调用者分配,然后由sem_init初始化。 当不需要使用与信号量关联的名字时,可改基于内存的信号量。彼此无亲缘关系的不同进程需要使用信号量时,通常使用 有名信号量。基于内存的信号量至少具有随进程的持续性,然而他们真正的持续性却取决于存放信号量内存的类型。只要含有 某个基于内存信号量的内存信号量的内存区有效时,该信号量就一直存在。 1>如果基于某个内存的信号量是由单个进程的各个线程共享的(sem_init的shared的参数为0),那么该信号量具有随进程持续性 ,当该进程终止时它也消失。 2>如果某个基于内存的信号量是在不同的进程间共享的(sem_init的shared参数为1),那么该信号量必须放在共享内存区中, 因而只要共享内存区仍然存在,该信号量也就继续存在。posix共享内存区和system v共享内存区都具有随内核的持续性。这意味着 服务器创建一个共享内存区,再该内存区中初始化一个posix基于内存的信号量,然后终止。一段时间后,一个或多个客户 可打开该共享内存区,访问存在其中的基于内存的信号量。 小心,下面的代码并不像预期的那样工作。 sem_t mysem; sem_init(&mysem,1,0) if(fork()==0){//child ..... sem_post(&mysem); } sem_wait(&mysem);//parent;wait for child 问题在于信号量mysem不在共享内存区中,fork出来的子进程通常不共享父进程的内存空间。子进程是在父进程内存空间 的副本上启动的,它跟共享内存区不是一回事。 进程间共享信号量 进程间共享信号量基于内存的信号量的规则很简单:信号量本身(其地址作为semz_init第一个参数的sem_t数据类型变量) 必须驻留在由所有希望共享他的进程所共享的内存区中,而且sem_init的第二个参数必须为1。 至于有名信号量,不同进程(不论彼此间有无亲缘关系)总是等够访问同一个有名的信号量, 只要他们在调用sem_open时,指定相同的名字就行了。即使对于某个给定的名字sem_open调用在每个进程中可能返回不同的指针 ,使用该指针的信号量函数(sem_post和sem_wait)所引用的仍然是同一个有名的信号量。 我们在调用sem_open返回某个sem_t数据类型变量的指针后接着调用fork,情况又会怎么杨呢?posix中有关fork 函数这样描述的“若果在父进程中打开任何信号仍然应在子进程中打开。”这意味着如下的代码是正确的: sem_t *mutex;/*global pointer that is copied across the fork*/ //parent creates named semaphore mutex=sem_open(name,O_CREAT|O_EXCL,FILE_MODE,0) if((child==fork())==0){ //child ...... sem_wait(mutex); } //parent sem_post(mutex); ..... 我们必须仔细搞清楚什么时候可以或者不可以在不同进程间共享某个信号量的原因: 一个信号量的状态可以包含在它本身的sem_t数据类型中,但是它还可能使用其他信息 (例如文件描述符)。我们将在下一章看到,一个system v信号量的唯一句柄是由semget 返回他的整数标识符。知道这个标识符的任何进程都可以访问该信号量。system v信号量 的所有的状态都包含在内核中,他们的整数标识符只是告诉内核具体引用那个信号量。 *************************************************************************************** posix共享内存区 内存映射文件(memory-mmaped file): 由open函数打开,由mmap函数把得到的描述符映射到当前进程地址空间中的一个文件) 共享内存区对象(shared——mmaped file): 由shm_open打开一个posix.1 IPC名字(也许是在文件系统中的一个路径名), 所返回的描述符由mmap函数映射到当前的进程地址空间。 这两种技术都要用到mmap技术,差别在于作为mmap的参数之一的描述符的获取手段:通过open或通过shm_pen。 posix内存映射文件 fd=open(pathname,....); ptr=mmap(...,fd,.....); posix共享内存区对象 fd=shm_open(name,...); ptr=mmap(....,fd,.....); posix内存共享区涉及到以下两个步骤要求。 (1)指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或者打开一个已存在的共享内存区对象。 (2)调用mmap把这个共享内存区映射到调用进程的地址空间。传递给shm_open的名字参数随后由希望共享内存区的 任何进程使用。 #include<sys/mman.h> int shm_open(const char *name,int oflag,mode_t mode); 返回:若成功则为非负的描述符,若出错则为-1; int shm_unlink(const char *name); 返回:若成功则为0,若出错则为-1; name参数:共享内存区的名字,可以自己定义一个名字; oflag参数必须或者包含有O_RDONLY(只读)标志,或者含有O_RDWR(读写)标志,还可以指定如下标志:O_CREAT,O_EXCL, 或O_TRUNC.如果随O_RDWR指定O_TRUNC标志,而且所需的内存空间已经存在,那么他将被截断成0长度。 mode参数指定权限位,他在指定了O_CREAT标志的前提下使用。注意与mq_open和sem_open函数不同,shm_open的mode参数 总是必须指定。如果没有指定O_CREAT标志,那么该参数可以指定为0; shm_open的返回值是一个整数描述符,他随后用做mmap的第5个参数。 shm_unlink函数删除一个共享内存区对象的名字。跟其他所有的unlink函数(删除文件系统中一个路径名的unlink,删除 一个posix消息队列的mq_unlink,以及删除一个posix有名信号量的sem_unlink)一样,删除一个名字不会影响对于其底层 支撑对象的现有引用,直到对于该引用全部关闭为止。删除一个名字仅仅防止后序的open或sem_open调用取得成功。 ftruncate和fstat函数 处理mmap的时候,普通文件或者共享内存区对象的大小都可以通过调用ftruncate修改 #include<unistd.h> posix就该函数对普通文件和共享内存区对象的处理定义稍有不同。 1>对于一个普通文件:如果该文件大小大于length参数,额外的数据就被丢弃掉。如果该文件大小小于length,那么 该文件是否修改以及其大小是否增长是未加说明的。实际对于一个普通文件,把他的大小扩张到length字节的可移植方法是: 先lseek到偏移为length-1处,然后write 1个字节的数据。所幸的是几乎搜有的unix实现都支持使用ftruncate扩展一个文件。 2>对于一个共享内存区对象:ftruncate把该对象的大小设置成length字节。 我们调用ftruncate来指定新创建的共享内存区对象的大小,或者修改已存在的对象的大小。当打开一个已存在的共享内存 区对象是,我们可以调用fstat来获取有关该对象的信息。 #include<sys/type.h> #include<sys/stat.h> int fstat(int fd,struct stat *buf); 返回:若成功则为0,若出错则为-1;
时间: 2024-10-12 09:21:05