linux网络编程之system v信号量(二)

时间:2022-09-20 15:16:30

今天迎来元旦假期的最后一天了,过得好快~昨天跟小伙伴们在军都滑雪陪儿爽,虽说上了两回中级道都摔得异常的惨烈,但是在初级道上学习"s"转弯还是有一些小心得,可以在要往高手迈进的前提,一定得要把基本功打扎实,否则会很惨烈~好了,在这无聊的下午,用博客继续充实自己。

上次学习了System v 信号量的一些概念,并封装了一些常用方法,下面会举例用信号量来实现进程互斥,来进一步加深对信号量的认识。

 先用图来描述一下这个程序的一个意图:

linux网络编程之system v信号量(二)

下面则开始实现,基于之前信号量的封装:

print.c:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>


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) */
};

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

int sem_create(key_t key)
{
    int semid;
    semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
    if (semid == -1)
        ERR_EXIT("semget");

    return semid;
}

int sem_open(key_t key)
{
    int semid;
    semid = semget(key, 0, 0);
    if (semid == -1)
        ERR_EXIT("semget");

    return semid;
}

int sem_setval(int semid, int val)
{
    union semun su;
    su.val = val;
    int ret;
    ret = semctl(semid, 0, SETVAL, su);
    if (ret == -1)
        ERR_EXIT("sem_setval");

    return 0;
}

int sem_getval(int semid)
{
        int ret;
        ret = semctl(semid, 0, GETVAL, 0);
        if (ret == -1)
                ERR_EXIT("sem_getval");

        return ret;
}

int sem_d(int semid)
{
    int ret;
    ret = semctl(semid, 0, IPC_RMID, 0);
    if (ret == -1)
        ERR_EXIT("semctl");

    return 0;
}

int sem_p(int semid)
{
    struct sembuf sb = {0, -1, 0};
    int ret;
    ret = semop(semid, &sb, 1);
    if (ret == -1)
        ERR_EXIT("semop");

    return ret;
}

int sem_v(int semid)
{
        struct sembuf sb = {0, 1, 0};
        int ret;
        ret = semop(semid, &sb, 1);
        if (ret == -1)
                ERR_EXIT("semop");

        return ret;
}

int semid;

int main(int argc, char *argv[])
{
    semid = sem_create(IPC_PRIVATE);//由于是父子进程,所以可以创建私有的信号量集
    sem_setval(semid, 0);//初始化信号量计数值为0
    pid_t pid;
    pid = fork();
    if (pid == -1)
        ERR_EXIT("fork");

    if (pid > 0)
    {//父进程
    }
    else
    {//子进程
    }
    return 0;
}

接下来则进行值打印:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>


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) */
};

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

int sem_create(key_t key)
{
    int semid;
    semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
    if (semid == -1)
        ERR_EXIT("semget");

    return semid;
}

int sem_open(key_t key)
{
    int semid;
    semid = semget(key, 0, 0);
    if (semid == -1)
        ERR_EXIT("semget");

    return semid;
}

int sem_setval(int semid, int val)
{
    union semun su;
    su.val = val;
    int ret;
    ret = semctl(semid, 0, SETVAL, su);
    if (ret == -1)
        ERR_EXIT("sem_setval");

    return 0;
}

int sem_getval(int semid)
{
        int ret;
        ret = semctl(semid, 0, GETVAL, 0);
        if (ret == -1)
                ERR_EXIT("sem_getval");

        return ret;
}

int sem_d(int semid)
{
    int ret;
    ret = semctl(semid, 0, IPC_RMID, 0);
    if (ret == -1)
        ERR_EXIT("semctl");

    return 0;
}

int sem_p(int semid)
{
    struct sembuf sb = {0, -1, 0};
    int ret;
    ret = semop(semid, &sb, 1);
    if (ret == -1)
        ERR_EXIT("semop");

    return ret;
}

int sem_v(int semid)
{
        struct sembuf sb = {0, 1, 0};
        int ret;
        ret = semop(semid, &sb, 1);
        if (ret == -1)
                ERR_EXIT("semop");

        return ret;
}

int semid;

void print(char op_char)
{
    int pause_time;
    srand(getpid());//以当前进程做为随机数的种子
    int i;
    for (i=0; i<10; i++)//各输出十次
    {
        sem_p(semid);//进行一个P操作
        printf("%c", op_char);
        fflush(stdout);//由于没有用\n,所以要想在屏幕中打印出字符,需要强制清空一下缓冲区
        pause_time = rand() % 3;//在0,1,2秒中随机
        sleep(pause_time);
        printf("%c", op_char);
        fflush(stdout);
        sem_v(semid);//进行一个V操作
        pause_time = rand() % 2;
        sleep(pause_time);//最后在0,1秒中随机
    }
}

int main(int argc, char *argv[])
{
    semid = sem_create(IPC_PRIVATE);//由于是父子进程,所以可以创建私有的信号量集
    sem_setval(semid, 0);//初始化信号量计数值为0
    pid_t pid;
    pid = fork();
    if (pid == -1)
        ERR_EXIT("fork");

    if (pid > 0)
    {//父进程
        sem_setval(semid, 1);//由于计数值初使为0,所以进行P操作时则会等待,为了进行p操作,则设置值为1
        print('O');
        wait(NULL);//等待子进程的退出
        sem_d(semid);//最后删除信号量值
    }
    else
    {//子进程
        print('X');
    }
    return 0;
}

下面编译运行来看一下效果:

linux网络编程之system v信号量(二)

从运行结果来看,o跟x一定是成对出现的,不可能出现ox一起打印,这就是信号量达到互斥作用的效果。

接下来用信号集来解决哲学家就餐问题,而且这一次信号集中信号量的个数不再是1个,而是多个,关于哲学家就餐问题可参考:http://www.cnblogs.com/webor2006/p/4149323.html,怎么解决呢?

linux网络编程之system v信号量(二)

下面回归到实际代码上来,由于这次的信号集中有多个信号量,所以这个实验中就不能用之前封装的方法了,需重新编写:

dining.c:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>


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) */
};

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

int semid;

int main(int argc, char *argv[])
{
    semid = semget(IPC_PRIVATE, 5, IPC_CREAT | 0666);//创建一个信号量集,里面包含五个信号量,这里也用私有的方式,因为会用子进程的方式模拟
    if (semid == -1)
        ERR_EXIT("semget");

    //将五个信号量的计数值都初始为1,资源都可用,模拟的是五把叉子
    union semun su;
    su.val = 1;
    int i;
    for (i=0; i<5; i++)
    {
        semctl(semid, i, SETVAL, su);
    }

    return 0;
}

linux网络编程之system v信号量(二)

linux网络编程之system v信号量(二)

而哲学家所做的事情如下:

linux网络编程之system v信号量(二)

具体实现如下:

linux网络编程之system v信号量(二)

接下来则实现wait_for_2fork()、free_2fork()两个函数:

linux网络编程之system v信号量(二)

结合图来想,就很容易明白这个算法,如下:

linux网络编程之system v信号量(二)

linux网络编程之system v信号量(二)

同样的,释放叉子类似:

linux网络编程之system v信号量(二)

至此解决哲学家就餐问题的代码就写完,下面来编译运行一下:

linux网络编程之system v信号量(二)

从中可以看到,没有出现死锁问题,下面从输出结果来分析一下:

linux网络编程之system v信号量(二)

从结果分析来看:不可能两个相邻的哲学家同时处于“吃”的状态,同时只能够有两个哲学家处于“吃”的状态。

接下来再来模拟一下死锁的情况,在模拟之前,注意:需手动将创建的信号量集给删掉,因为刚才运行是强制关闭程序的,另外在实现之前,需要思考一下怎么样能产生死锁,其实思路很简单,就是申请叉子的时候,一个个申请,而不是当只有两个都有的情况下才能申请,所以,修改代码如下:

linux网络编程之system v信号量(二)

接下来实现wait_1fork():

linux网络编程之system v信号量(二)

下面编译运行:

linux网络编程之system v信号量(二)

从结果来看确实是阻塞了,由于都拿起了左边的叉子,而且都在等待右边叉子,而都没人释放左叉子,于是乎死锁就产生了。

最后贴上完整代码:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>


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) */
};

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

#define DELAY (rand() % 5 + 1)//定义一个睡眠时间,1~5秒中
int semid;


//等待一把叉子
int wait_1fork(int no)
{
    struct sembuf sb = {no, -1, 0};
    int ret;
    ret = semop(semid, &sb, 1);
    if (ret == -1)
        ERR_EXIT("semop");

    return ret;
}

//等待左右两个叉子
void wait_for_2fork(int no)
{
    int left = no;
    int right = (no + 1) % 5;

    struct sembuf buf[2] = {
        {left, -1, 0},
        {right, -1, 0}
    };

    semop(semid, buf, 2);
}

//释放左右两信叉子
void free_2fork(int no)
{
    int left = no;
    int right = (no + 1) % 5;

    struct sembuf buf[2] = {
            {left, 1, 0},
            {right, 1, 0}
    };

    semop(semid, buf, 2);
}

void philosophere(int no)
{
    srand(getpid());//设置随机的种子
    for (;;)
    {//不断循环执行
        /*
        printf("%d is thinking\n", no);//首先思考
        sleep(DELAY);
        printf("%d is hungry\n", no);//饿了
        wait_for_2fork(no);
        printf("%d is eating\n", no);//当获取到了左右两把叉子,则开吃
        sleep(DELAY);
        free_2fork(no);//吃完则放下左右两把叉子
        */

        int left = no;
        int right = (no + 1) % 5;
        printf("%d is thinking\n", no);
        sleep(DELAY);
        printf("%d is hungry\n", no);
        wait_1fork(left);
        sleep(DELAY);
        wait_1fork(right);
        printf("%d is eating\n", no);
        sleep(DELAY);
        free_2fork(no);
    }
}

int main(int argc, char *argv[])
{
    semid = semget(IPC_PRIVATE, 5, IPC_CREAT | 0666);//创建一个信号量集,里面包含五个信号量,这里也用私有的方式,因为会用子进程的方式模拟
    if (semid == -1)
        ERR_EXIT("semget");

    //将五个信号量的计数值都初始为1,资源都可用,模拟的是五把叉子
    union semun su;
    su.val = 1;
    int i;
    for (i=0; i<5; i++)
    {
        semctl(semid, i, SETVAL, su);
    }

    //接下来创建四个子进程,加上父进程则为5个,来模拟5个哲学家
    int no = 0;
    pid_t pid;
    for (i=1; i<5; i++)
    {
        pid = fork();
        if (pid == -1)
            ERR_EXIT("fork");

        if (pid == 0)
        {
            no = i;
            break;
        }
    }

    philosophere(no);

    return 0;
}

好了,今天学到这,肚子饿了,吃饭下次继续~