Linux下IPC 之 信号量

 进程互斥

由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。在进程中涉及到互斥资源的程序段叫临界区。

进程同步
进程同步指的是多个进程需要相互配合共同完成一项任务。

进程间通信目的
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

死锁

死锁是指多个进程之间相互等待对方的资源,而在得到对方资源之前又不释放自己的资源,这样,造成循环等待的一种现象。如果所有进程都在等待一个不可能发生的事,则进程就死锁了。
死锁产生的必要条件
互斥条件
  进程对资源进行排它性使用,即在一段时间内某资源仅为一个进程所占用。
请求和保持条件
  当进程因请求资源而阻塞时,对已获得的资源保持不放。
不可剥夺条件
  进程已获得的资源在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
环路等待条件
  各个进程组成封闭的环形链,每个进程都等待下一个进程所占用的资源

防止死锁办法
资源一次性分配:破坏请求和保持条件
可剥夺资源:破坏不可剥夺条件
资源有序分配法:破坏循环等待条件

死锁避免
预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。

银行家算法的思想就是尝试进行资源的分配,如果资源的分配进入了不安全状态,就撤销资源的分配,使得进程处于等待的状态,否则就进行资源的分配。


哲学家就餐问题
五个哲学家围在一个圆桌就餐,每个人都必须拿起两把叉子才能用餐
哲学家就餐问题解法
    服务生解法  相当于是引入一个中间层
    最多4个哲学家同时去拿取筷子
    仅当一个哲学家两边筷子都可用时才允许他拿筷子  相当于是一次性分配好所有的资源
    给所有哲学家编号,奇数号的哲学家必须首先拿左边的筷子,偶数号的哲学家则反之

信号量
信号量
  互斥:P、V在同一个进程中
  同步:P、V在不同进程中
信号量值含义
  S>0:S表示可用资源的个数
  S=0:表示无可用资源,无等待进程
  S<0:|S|表示等待队列中进程个数  阻塞在信号量的进程

用伪代码表示就是

struct semaphore

{    int value;  //如果是1,相当于是一个竞争锁,起到资源竞争的作用了。

    pointer_PCB queue; }

P原语 消耗资源 加锁  临界资源 临界区

P(s)

{

         s.value = s.value--;

         if (s.value < 0)

         {

                  该进程状态置为等待状状态

                  将该进程的PCB插入相应的等待队列s.queue末尾

         }

}

V原语 归还资源 解锁 临界资源 临界区

V(s)

{

         s.value = s.value++;

         if (s.value < =0)

         {

                 唤醒相应等待队列s.queue中等待的一个进程

                 改变其状态为就绪态

                 并将其插入就绪队列

         }

}

PV 操作从硬件级别是如何实现的, 关中断。

struct semid_ds { // 信号量结构体 linux维护的一个信号量结构体
struct ipc_perm sem_perm;  /* Ownership and permissions */
time_t         sem_otime; /* Last semop time */
time_t         sem_ctime; /* Last change time */
unsigned short  sem_nsems; /* No. of semaphores in set */
};

信号量集函数
int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd, ...);
int semop(int semid, struct sembuf *sops, unsigned nsops);   

semget函数用来创建和访问一个信号量集
int semget(key_t key, int nsems, int semflg);
key: 信号集的名字
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该信号集的标识码;失败返回-1

semop函数用来操作信号量集合 PV 操作
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向一个结构数值的指针
nsops:信号量的个数
返回值:成功返回0;失败返回-1
semop函数续
sembuf结构体:
struct sembuf {
    short sem_num;
    short sem_op;
    short sem_flg;   //默认设置为0就是阻塞的情况
};
sem_num是信号量的编号。
sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用
sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO

shmctl函数 用于控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
semid: 由semget返回的信号集标识码
semnum: 信号集中信号量的序号
cmd: 将要采取的动作(有三个可取值)
最后一个参数根据命令不同而不同
成功返回0;失败返回-1

信号量的操作很繁琐,因此需要设计一套API出来,方便操作控制

//创建信号量
int sem_creat(key_t key)
{
    int semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL); 
    if(semid == -1)
    {
        perror("semger");
        if(errno == EEXIST)
        {
            printf("检测到信号量已经存在了!\n");
            return 0;
        }
    }
}
//获取信号量
int sem_open(key_t key)
{
    int semid = semget(key, 1, 0666);
    if(semid == -1)
    {
        perror("semger");
        return -1;
    }
}


//设置信号量的个数
int sem_setval(int semid, int val)
{
    union semun su;
    su.val = val;
    return semctl(semid, 0, SETVAL, su);
}
//获取信号量的个数
int sem_getval(int semid)
{
    return semctl(semid, 0, GETVAL);
}

//信号量的P操作
int sem_p(int semid)
{
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = -1;
    buf.sem_flg = 0;
    return semop(semid, &buf, 1);
}

//信号量的V操作
int sem_v(int semid)
{
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = +1;
    buf.sem_flg = 0;
    return semop(semid, &buf, 1);
}

//在设置信号量个数的时候需要用到的数据结构
union semun {
int              val;    // Value for SETVAL 
struct semid_ds *buf;    // Buffer for IPC_STAT, IPC_SET 
unsigned short  *array;  // Array for GETALL, SETALL 
struct seminfo  *__buf;  // Buffer for IPC_INFO(Linux-specific) 
};

sem_flg 为IPC_NOWAIT 表示为非阻塞的方式

sem_flg 为SEM_UNDO, 如果执行了一个P操作,如果进程死掉了后者是结束了没有执行V操作,可以自动恢复成原来的状况,要不然就会少了一个资源使用量。是操作系统进行管理的V操作。

猜你喜欢

转载自www.cnblogs.com/randyniu/p/9185204.html