linux信号量解析

Linux  内核中的信号量使用和用户态的信号量使用有所不同,
1、内核信号量,由内核控制路径使用。
2、用户态信号量分为两种,一种为POSIX,另一种为 SYSTEM V

POSIX信号量又分为有名信号量和无名信号量。
有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名信号量,其值保存在内存中。

SYSTEM V接口函数比较复杂,主要区别是函数没有下划线;POSIX信号量是新兴信号量函数,有下划线函数。

内核中信号量的构成以及使用:
内核信号量的构成
内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而,当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源被释放时,进程才再次变为可运行。
只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内核信号量。
内核信号量是struct semaphore类型的对象,它在<asm/semaphore.h>中定义
struct semaphore {
   atomic_t count;
   int sleepers;
   wait_queue_head_t wait;
  }
count:相当于信号量的值,大于0,资源空闲;等于0,资源忙,但没有进程等待这个保护的资源;小于0,资源不可用,并至少有一个进程等待资源。
wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。
sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠
内核信号量中的等待队列(删除,没有联系)
上面已经提到了内核信号量使用了等待队列wait_queue来实现阻塞操作。当某任务由于没有某种条件没有得到满足时,它就被挂到等待队列中睡眠。当条件得到满足时,该任务就被移出等待队列,此时并不意味着该任务就被马上执行,因为它又被移进工作队列中等待CPU资源,在适当的时机被调度。 内核信号量是在内部使用等待队列的,也就是说该等待队列对用户是隐藏的,无须用户干涉
内核信号量的相关函数
初始化:
void sema_init (struct semaphore *sem, int val);
void init_MUTEX (struct semaphore *sem); //将sem的值置为1,表示资源空闲
void init_MUTEX_LOCKED (struct semaphore *sem); //将sem的值置为0,表示资源忙
申请内核信号量所保护的资源:
void down(struct semaphore * sem); // 可引起睡眠
int down_interruptible(struct semaphore * sem); // down_interruptible能被信号打断
int down_trylock(struct semaphore * sem); // 非阻塞函数,不会睡眠。无法锁定资源则马上返回
释放内核信号量所保护的资源:
void up(struct semaphore * sem);


------------------------------------------------------我是分割线----------------------------------------

SYSTEM V信号量
这是信号量值的集合,而不是单个信号量。相关的信号量操作函数由<sys/ipc.h>引用。

System V 信号量在内核中维护,其中包括二值信号量 、计数信号量、计数信号量集。
二值信号量 : 其值只有0、1 两种选择,0表示资源被锁,1表示资源可用;
计数信号量:其值在0 和某个限定值之间,不限定资源数只在0 1 之间;
计数信号量集 :多个信号量的集合组成信号量集
内核维护的信号量集结构信息如下:定义在头文件<sys/sem.h>

复制代码
struct semid_ds {
    struct     ipc_perm     sem_perm;
    struct     sem          *sem_base;
    ushort                  sem_nsems;
    time_t                  sem_otime;
    time_t                  sem_ctime;
};
复制代码

其中ipc_perm 结构是内核给每个进程间通信对象维护的一个信息结构,其成员包含所有者用户id,所有者组id、创建者及其组id,以及访问模式等;semid_ds结构体中的sem结构是内核用于维护某个给定信号量的一组值的内部结构,其结构定义:

struct sem {
   int semval;     /* current value */
   int sempid;     /* pid of last operation */
   struct list_head sem_pending; /* pending single-sop operations */
 };

其中senval变量代表当前信号量的值,sempid 为最后一个成功操作该信号量的进程id,该结构体在内核以双向链表进行 维护
semid_ds结构体中的sem_nsems成员代表该信号量标示符的信号量个数
主要函数介绍:

创建一个信号量或访问一个已经存在的信号量集。
int semget(key_t key, int nsems, int semflg);
该函数执行成功返回信号量标示符,失败返回-1
参数key是通过调用ftok函数得到的键值,nsems代表创建信号量的个数,如果只是访问而不创建则可以指定该参数为0,我们一旦创建了该信号量,就不能更改其信号量个数,只要你不删除该信号量,你就是重新调用该函数创建该键值的信号量,该函数只是返回以前创建的值,不会重新创建;
semflg 指定该信号量的读写权限,当创建信号量时不许加IPC_CREAT ,若指定IPC_CREAT |IPC_EXCL则创建是存在该信号量,创建失败。

打开一个信号量集后,对其中一个或多个信号量的操作。
int semop(int semid, struct sembuf *sops, unsigned nsops);
该函数执行成功返回0,失败返回-1;
第一个参数semid 为信号量标示符;nops为第二个参数的操作数组的个数,第二个参数sops为一个结构体数组指针,结构体定义在sys/sem.h中,结构体如下

struct sembuf {
   unsigned short sem_num; /* semaphore index in array */
   short sem_op; /* semaphore operation */
   short sem_flg; /* operation flags */
};
sem_num 操作信号的下标,其值可以为0 到nops
sem_flg为该信号操作的标志:其值可以为0、IPC_NOWAIT 、 SEM_UNDO
0 在对信号量的操作不能执行的情况下,该操作阻塞到可以执行为止;
IPC_NOWAIT 在对信号量的操作不能执行的情况下,该操作立即返回;
SEM_UNDO当操作的进程推出后,该进程对sem进行的操作将被取消;
sem_op取值 >0 则信号量加上它的值,等价于进程释放信号量控制的资源
sem_op取值 =0若没有设置IPC_NOWAIT, 那么调用进程将进入睡眠状态,直到信号量的值为0,否则进程直接返回
sem_op取值 <0则信号量加上它的值,等价于进程申请信号量控制的资源,若进程设置IPC_NOWAIT则进程再没有可用资源情况下,进程阻塞,否则直接返回。

对信号量执行各种控制操作。
int semctl(int semid, int semnum, int cmd, ...);
该函数执行成功返回非负值,失败返回-1
参数semid为信号集的标识符,参数 semnum标识一个特定信号,该参数仅用于 SETVAL、GETVAL、GETPID命令

-------------------------------------------------------------------我是分割线----------------------------------------------------------------

1.无名信号量
    无名信号量的创建就像声明一般的变量一样简单,例如:sem_t  sem_id。然后再初始化该无名信号量,之后就可以放心使用了。
    无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程(线程)的共享变量,这两个条件是缺一不可的。

    常见的无名信号量相关函数:sem_destroy


    int sem_init(sem_t *sem, int pshared, unsigned int value);
    1) pshared ==0 用于同一多线程的同步;
    2)若pshared>0 用于多个相关进程间的同步(即由fork产生的)

    int sem_getvalue(sem_t *sem, int *sval);
    取回信号量sem的当前值,把该值保存到sval中。
    若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值:
     1) 返回0
     2) 返回阻塞在该信号量上的进程或线程数目
     linux采用返回的第一种策略。


    sem_wait(或sem_trywait)相当于P操作,即申请资源。
    int sem_wait(sem_t *sem);     // 这是一个阻塞的函数    
    测试所指定信号量的值,它的操作是原子的,
    若sem>0,那么它减1并立即返回。
    若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。
    int sem_trywait(sem_t *sem);   // 非阻塞的函数
    其他的行为和sem_wait一样,除了:
    若sem==0,不是睡眠,而是返回一个错误EAGAIN。 
    sem_post相当于V操作,释放资源。


    int sem_post(sem_t *sem);
    把指定的信号量sem的值加1;
    呼醒正在等待该信号量的任意线程。 
    注意:在这些函数中,只有sem_post是信号安全的函数,它是可重入函数

------------------------------------------------我是分割线---------------------------------------------------------

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)有名信号量在无相关进程间的同步
前面已经说过,有名信号量是位于共享内存区的,那么它要保护的资源也必须是位于共享内存区,只有这样才能被无相关的进程所共享。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值