进程通信2——共享内存,信号量

共享内存:
共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
创建共享内存分为两步:
1、创建共享内存,使用shmget函数
2、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
案例:

typedef struct _shm
{
    int flag;
    char msg[256];
}SHM;

int main()
{
    // 1、创建或者获取一个共享内存
    int shmid = shmget((key_t)1234, sizeof(SHM), 0666 | IPC_CREAT);
    if (shmid == -1)
    {
        perror ("shmget");
        return -1;
    }

    // 2、将共享内存映射到当前的进程空间
    SHM* pshm = (SHM*)shmat(shmid, NULL, 0);
    if(pshm == (SHM*)-1)
    {
        perror ("shmat");
        return -1;
    }

    strcpy (pshm->msg, "hello");

    // 解除共享内存映射,解除是值当前进程不能再使用共享内存
    shmdt(pshm);


    shmctl(shmid, IPC_RMID, NULL);


    return 0;
}

用函数shmget和函数shmat可以创建一个共享内存并将它映射到当前的进程空间,而函数shmdt则可以解除共享内存的映射,最后的函数shmctl则可以对共享内存进行控制,包括删除共享内存的功能。

信号量:
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行进程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个进程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。

由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

信号量的创建:
int semget(key_t key, int num_sems, int sem_flags);
第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。

第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。

第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

semget函数成功返回一个相应信号标识符(非零),失败返回-1.
案例:

// 创建一个信号量
int sem_id = semget((key_t)5678, 1, 0666 | IPC_CREAT);

信号量初始化即P,V操作:

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) */
};
// 信号量的初始化函数
int sem_init(int sem_id)
{
    union semun sem;
    sem.val = 1;
    int ret = semctl(sem_id, 0, SETVAL, sem);
    return ret;
}

// 信号量的 P 操作
int sem_p(int sem_id)
{
    struct sembuf sem;
    sem.sem_num = 0;
    sem.sem_op  = -1;
    sem.sem_flg = SEM_UNDO;
    int ret = semop(sem_id, &sem,1);

    return ret;
}

// 信号量的 v 操作
int sem_v(int sem_id)
{
    struct sembuf sem;
    sem.sem_num = 0;
    sem.sem_op  = 1;
    sem.sem_flg = SEM_UNDO;
    int ret = semop(sem_id, &sem,1);

    return ret;
}

这里我们用到了一个函数semop来进行信号量的P, V操作。这其中用到了一个结构体sembuf,sembuf结构的定义如下:
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
最后可以销毁信号量:

int sem_del(int sem_id)
{
    int ret = semctl(sem_id, 0, IPC_RMID);
    return ret;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章