进程间通信

共享内存:
正常来说,两个进程被父亲生下来之后,即使是亲兄弟,他们两个内存也是独立的。
如果提前约定:在内存中有一个空间,在以后兄弟都可以在那里拿数据。(临界区)
除了父进程可以为子进程创建共享内存,两个完全不相关的进程也可以通过某种联系,创建共享内存。
有亲缘关系的进程,和无亲缘关系的进程
亲缘关系:父亲,孩子,亲兄弟

  1. shm share_memory
    亲缘关系:父亲 + 孩子 / 亲兄弟之间
    shmget 分配一个系统内存共享段
    #include <sys/ipc.h>//ipc,进程间通讯
    #include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg); // size 多大,shmflg 类型
    返回值与key有关,key相同,返回的id相同
    返回的是共享内存段的id
    一个共享内存段的大小,等同于上取整到n * page_size的大小(一般是4k)
    当key 中取值为IPC_PRIVATE 或者 shmflg 中设置了IPC_PRIVATE 那么就会建立一个共享内存,否则不会有共享内存创建。
    如果shmflg 中设定了IPC_CREAT | IPC_EXCL 但是这个收共享内存段已经存在,会创建失败。
    shmflg:
    IPC_CREATE: 创建一个段,如果这个flag 没有被用,那么shmget 会找到一个与key 值有关的段,然后检查用户是否有权限来使用这个段。
    IPC_EXCL : 伴随IPC_CREAT 使用,确保这个调用来创建段,如果段已经存在了,那么调用失败。
    将id获取地址:shmat
    这个函数返回一个区域的id,我们无法直接访问内存,所以我们需要id 转换成地址。
  2. shmat : 通过id 找到地址
    void *shmat(int shmid, const void *shmaddr,int shmflg);
    int shmdt(const void *shmaddr);
    shmat 通过shmid 来获得共享内存段的地址空间
    相同的key 拿到的地址可能不同,因为这个地址,是自己空间的地址,附着到的地址被shmaddr 指定。
    shmat 中shmaddr 的集中情况
    1. 如果shamaddr 为空, 那么自动自己选择一个合适的页来存放段。
    2. 如果shmaddr 不是空,但是 shmflg 中设置了SHM_RND ,那么存放在shmaddr 上的地址向下取整来靠近SHMLBA
    3. 否则,shmaddr 一定是一个页大小的整数倍。
      执行成功的shmat 调用会更新成员shimid_ds 结构体:
      1. shm_atime 被设置为最近的时间
      2. shm_lpid 被设置进程id
      3. shm_nattch 增加1
        执行成功shmdt 调用会更新:
      4. shm_Dtime 被设置为最近的时间
      5. shm_lpid 被设置进程id
      6. shm_nattch 减1
        shmat 执行成功,返回共享段的附着地址,失败的话返回-1
        shmdt 执行成功,返回0,失败的话返回-1
  3. ftok
    ket_t ftok(const *pathname, int proj_id); // 将路径名和一个 project id 转换成一个key
    这个key 返回一个id 然后通过这个id 使用shmget 得到一个段id 然后通过shmat 得到地址。
//代码版本1
#include "head.h"
#define INS 5

struct Num {
    int now;
    int sum;
};

struct Num *share_memory;

int main() {
    pid_t pid;
    int x = 0, shmid;
    key_t key = ftok(".", 2021);
    if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL)) < 0) {
        perror("shmget");
        exit(1);
    }
    share_memory = (struct Num*) shmat(shmid, NULL, 0);
    if (share_memory == NULL) {
        perror("shmat");
        exit(1);
    }
    for (int i = 1; i <= INS ;i++) {
        if ((pid == fork()) < 0) {
            perror("fork");
        }
        x = i;
        if (pid == 0) break;
    }
    return 0;
}

但是这样的代码运行第一次可以,运行第二次的时候,共享段已经被创建,需要删除文件才能继续运行。
用ipcs 查看共享段。
ipcrm 删除共享段

// 代码版本2, 我们先忽略共享段的问题
#include "head.h"
#define INS 5

struct Num {
    int now;
    int sum;
};

struct Num *share_memory;


void do_add() {
    while (1) {
        if (share_memory->now > 100) break;
        share_memory->now++;
        share_memory->sum += share_memory->now;
    }
    return ;
}

int main() {
    pid_t pid;
    int x = 0, shmid;
    key_t key = ftok(".", 2021);
    if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL)) < 0) {
        perror("shmget");
        exit(1);
    }
    share_memory = (struct Num*) shmat(shmid, NULL, 0);
    if (share_memory == NULL) {
        perror("shmat");
        exit(1);
    }
    share_memory->now = 0;
    share_memory->sum = 0;
    for (int i = 1; i <= INS ;i++) {
        if ((pid == fork()) < 0) {
            perror("fork");
        }
        x = i;
        if (pid == 0) break;
    }

    if (pid == 0) {
        do_add();
    } else {
        for (int i = 1; i <= INS; i++) {
            wait(NULL);
        }
    }
    printf("share_memory->sum = %d\n", share_memory->sum);
    return 0;
}

运行之后会发现段错误,访问到了自己不该访问的问题
可能在哪里出现?
我们只有在share_memory 时候访问到地址。。。
所以在父进程为share_memory 进程赋值之后做了一次输出,发现的确没有输出,出现了错误。确定了段错误的位置。
所以追溯到share_memory 之后, perror 输出一下,发现提示permission deny
于是注意到 在声明id 的时候,没有赋予可读可写的权限,于是 在flag 改为 IPC_CREAT | IPC_EXCL | 0666

#include "head.h"
#define INS 5

struct Num {
    int now;
    int sum;
};

struct Num *share_memory;


void do_add(int x) {
    while (1) {
        if (share_memory->now > 1000) break;
        share_memory->sum += share_memory->now;
        share_memory->now++;
        printf("Child : %d, share_memory->now : %d, share_memory->sum : %d\n", \
               x, share_memory->now, share_memory->sum);
        usleep(100);
    }
    return ;
}

int main() {
    pid_t pid;
    int x = 0, shmid;
    key_t key = ftok(".", 2021);
    if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    share_memory = (struct Num*) shmat(shmid, NULL, 0);
    if (share_memory < 0) {
        perror("shmat");
        exit(1);
    }
    share_memory->now = 0;
    share_memory->sum = 0;
    for (int i = 1; i <= INS ;i++) {
        if ((pid = fork()) < 0) {
            perror("fork");
        }
        x = i;
        if (pid == 0) break;
    }

    if (pid == 0) {
        do_add(x);
    } else {
        for (int i = 1; i <= INS; i++) {
            wait(NULL);
        }
    }
    printf("share_memory->sum = %d, My Pid : %d\n", share_memory->sum, getpid());
    return 0;
}

上面这个代码可以成功的执行出,在一核的机器上只有一个cpu 有极小的可能出错,但是还有有可能,所以为了避免这种可能,我们采用锁结构。

一核 一次只运行一个进程,为什么还会有小的可能出错?
在do_add 中,now 值自增和 sum 累加的操作不是原子的,有一种可能是,now 刚刚自增,但是时间片到了,运行下一个进程,这样导致了一个漏加的可能性。

加锁的本质是给资源加锁, 锁住的是资源,不是进程.

互斥锁:
我们可以用线程互斥锁。
线程互斥锁
mutex:互斥
pthread_mutex_init:线程互斥锁初始化
pthread_mutex_unlock:线程互斥锁解锁
pthread_mutex_destroy:线程互斥锁销毁
pthread_mutex_lock:线程互斥锁的锁
pthread_mutex_trylock:线程互斥锁的尝试锁

  1. pthread_mutex_init :
    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t mutexattr) 锁指针,锁属性指针

  2. pthread_mutexattr_
    pthread_mutexattr_setpshared : set process shared
    int pthread_mutexattr_setpshared(const pthread_mutexattr_t *arr, int pshared);
    pthread_mutexattr_init : 互斥锁属性初始化
    int pthread_mutexattr_init(pthread_mutexattr_t *attr);
    这把锁为了让所有进程都看到,所以这把锁声明在共享区。

// 加锁之后的代码
#include "head.h"
#define INS 5

struct Num {
    int now;
    int sum;
    pthread_mutex_t mutex;
};

struct Num *share_memory;


void do_add(int x) {
    while (1) {
        pthread_mutex_lock(&share_memory->mutex);
        if (share_memory->now > 1000) {
            pthread_mutex_unlock(&share_memory->mutex);
            break;
        };
        share_memory->sum += share_memory->now;
        share_memory->now++;
        pthread_mutex_unlock(&share_memory->mutex);
        printf("Child : %d, share_memory->now : %d, share_memory->sum : %d\n", \
               x, share_memory->now, share_memory->sum);
        usleep(100);
    }
    return ;
}

int main() {
    pid_t pid;
    int x = 0, shmid;
    key_t key = ftok(".", 2021);
    if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    share_memory = (struct Num*) shmat(shmid, NULL, 0);
    if (share_memory < 0) {
        perror("shmat");
        exit(1);
    }
    share_memory->now = 0;
    share_memory->sum = 0;

    // 锁初始化
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, 1);

    pthread_mutex_init(&(share_memory->mutex), &attr);

    for (int i = 1; i <= INS ;i++) {
        if ((pid = fork()) < 0) {
            perror("fork");
        }
        x = i;
        if (pid == 0) break;
    }

    if (pid == 0) {
        do_add(x);
    } else {
        for (int i = 1; i <= INS; i++) {
            wait(NULL);
        }
    }
    shmdt(share_memory);
    perror("shmdt");
    printf("share_memory->sum = %d, My Pid : %d\n", share_memory->sum, getpid());
    return 0;
}

使用锁,需要初始化一个锁属性,然后在需要的地方放置锁,初始化锁

锁的条件变量 pthread_cond_…
锁条件变量是一个同步发生的设备,允许线程们当遇到某个条件去挂起或者放弃CPU.
条件变量的基本操作是:1.发出一个信号,2.然后等待这个信号,并在挂起的线程的会在收到这个信号执行.

类比:朋友叫我吃饭,我不饿,饿的时候再吃.朋友这个线程会被挂起,然后我饿的时候,发出一个信号,饿!, 朋友收到这个信号,然后运行.

条件变量一定是在锁的基础上的,这个是为了避免信号发出在等待这个信号的线程之前.(避免我说完饿了,朋友才开始等着去接收这个信号,于是错过了信号).

pthread_cond_init 初始化一个条件变量.初始化这个条件变量可能需要cond_attr(条件变量属性), 如果没有这个属性,也就是cond_attr 被设置为空的话,那么Linux 线程支持没有属性的条件变量,因此条件属性是被忽视的.

可以用PTHREAD_COND_INITIALIZE 静态的初始化条件变量.

条件信号(pthread_cond_signal)只会重启一个正在等待的线程,如果没有线程等待这个条件变量,那么什么都不会发生,如果很多个条件变量在等你,那么有一个被重启,不过不确定是哪一个…

条件变量广播(pthread_cond_broadcast)重启所有等待的线程,没有等待的情况下同上.

等待条件变量(pthread_cond_wait) 会自动的解互斥锁(自动执行pthread_unloock_mutex),然后等待条件信号响应,线程会被挂起,不占用任何时间,直到信号响应.在等待条件变量之前,互斥锁一定是被锁上的,在被唤醒之后重新获得互斥锁.
所以上述整体流程大概是:先加锁->发现需要等待信号:解锁->等待->唤醒->加锁.

上述的解锁操作是自动执行的,所以,如果所有的线程等待这个信号来占据锁,需要保证在加锁和等待条i按变量的时候,条件变量没有发信号,也就是原子性.

条件超时(pthread_cond_timedwait) 顾名思义,超时之后就不等了

条件销毁(pthread_cond_destory)

// 完整无BUG 代码
#include "head.h"
#define INS 5

struct Num {
    int now;
    int sum;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
};

struct Num *share_memory;


void do_add(int x) {
    while (1) {
        pthread_mutex_lock(&share_memory->mutex);
        // printf("Child : %d Begin wait!\n", x);
        pthread_cond_wait(&share_memory->cond, &share_memory->mutex);
        int flag = 0;
        for (int i = 0; i < 100; i++) {
            if (share_memory->now > 1000) {
                pthread_mutex_unlock(&share_memory->mutex);
                usleep(100); //小小休息一下,防止在等待之前接收信号
                pthread_cond_signal(&share_memory->cond);
             // printf("Child %d exit\n" , x);
                shmdt(share_memory); // 每个子进程在退出之前要把自己的附着删掉
                printf("Child %d exit\n", x);
                exit(0);
                break;
            };
            share_memory->sum += share_memory->now;
            share_memory->now++;
            printf("Child : %d, share_memory->now : %d, share_memory->sum : %d\n", \
                x, share_memory->now, share_memory->sum);
            fflush(stdout);
        }
        pthread_mutex_unlock(&share_memory->mutex);
        pthread_cond_signal(&share_memory->cond);
    }
    return ;
}

int main() {
    pid_t pid;
    int x = 0, shmid;
    key_t key = ftok(".", 2021);
    if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    share_memory = (struct Num*) shmat(shmid, NULL, 0);
    if (share_memory < 0) {
        perror("shmat");
        exit(1);
    }
    share_memory->now = 0;
    share_memory->sum = 0;

    // 锁初始化
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, 1);

    //初始化环境变量
    pthread_condattr_t c_attr;
    pthread_condattr_init(&c_attr);
    pthread_condattr_setpshared(&c_attr, 1);
    pthread_cond_init(&share_memory->cond, &c_attr);

    pthread_mutex_init(&(share_memory->mutex), &attr);

    for (int i = 1; i <= INS ;i++) {
        if ((pid = fork()) < 0) {
            perror("fork");
        }
        x = i;
        if (pid == 0) break;
    }

    if (pid == 0) {
        do_add(x);
    } else {
        usleep(500);// 不休息的话,可能发信号的时候,子进程还没有等待
        printf("Parent!\n");
        pthread_cond_signal(&share_memory->cond);
        for (int i = 1; i <= INS; i++) {
            wait(NULL);
        }
    }
    printf("share_memory->sum = %d, My Pid : %d\n", share_memory->sum, getpid());
    shmdt(share_memory);// 删除父进程的附着部分
    shmctl(shmid, IPC_RMID, NULL); // 只有在shmctl 中才能删除共享段。
    perror("shmctl");
    return 0;
}

出现file exits 是因为我们在操作系统中创建的共享段没有删除,只是除去了共享段的所有附着进程.

shmctl 可以控制共享段部分
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
通过cmd 的部分来控制shmid指向的共享段。

struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };

在shmat 和 shmdt 中提到了shm_atime, shm_dtime, shm_nattch

struct ipc_perm {
               key_t          __key;    /* Key supplied to shmget(2) */
               uid_t          uid;      /* Effective UID of owner */
               gid_t          gid;      /* Effective GID of owner */
               uid_t          cuid;     /* Effective UID of creator */
               gid_t          cgid;     /* Effective GID of creator */
               unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
               unsigned short __seq;    /* Sequence number */
           };

cmd :

  1. IPC_STAT: 从内核中拷贝shmid 所内涵的信息,到这个函数的第三个参数中,在执行这个命令的时候的前提是该进程对共享段可读.
  2. IPC_SET: 声明好一个shmid_ds信息,然后执行这个命令,会向内核中写入shmid的信息,在这个时候会更新shm_ctime,但不是所有内容都可以改变,可以改变的有shm_perm.uid, shm_perm.gid,和 shm_perm.mode 后9位, 只有当前这个进程的所属用户或者权限更高的用户(比如 root) c爱可以改变这些值.
  3. IPC_RMID: 摧毁标记段,这个段在摧毁前需要保证最后一个附着进程也需要解除附着,调用者必须是创造这个共享段的进程,或者是更权威的进程,在这个命令中 buf 这个参数被忽略.调用者必须保证这个段最终被摧毁,否则这个内存页会一直保留在内存的交换区.
  4. IPC_INFO: 返回系统共享内存的宽度限制和参数,这个buf 的数据结构是shmimfo 所以用的时候需要进行一个类型转换.
struct shminfo {
                         unsigned long shmmax; /* Maximum segment size */
                         unsigned long shmmin; /* Minimum segment size;
                                                  always 1 */
                         unsigned long shmmni; /* Maximum number of segments */
                         unsigned long shmseg; /* Maximum number of segments
                                                  that a process can attach;
                                                  unused within kernel */
                         unsigned long shmall; /* Maximum number of pages of
                                                  shared memory, system-wide */
                     };
  1. SHM_INFO: 返回一个shm_info 结构,包含了系统资源被共享段使用的信息.
struct shm_info {
                         int           used_ids; /* # of currently existing
                                                    segments */
                         unsigned long shm_tot;  /* Total number of shared
                                                    memory pages */
                         unsigned long shm_rss;  /* # of resident shared
                                                    memory pages */
                         unsigned long shm_swp;  /* # of swapped shared
                                                    memory pages */
                         unsigned long swap_attempts;
                                                 /* Unused since Linux 2.4 */
                         unsigned long swap_successes;
                                                 /* Unused since Linux 2.4 */
                     };

以下命令为Linux 独有命令
1.SHM_STAT: 返回一个shmid_ds 数据结构,对比与IPC_STAT 不同的是,shmid 不是shmget 返回的共享段的id, 而是在内核中,共享段的索引

通过以下命令调用者可以阻止或者允许共享段的交换(换出内存)

  1. SHM_LOCK: 阻止共享段的交换
  2. SHM_UNLOCK: 解锁该段,允许他交换.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值