周末整理笔记,发现已经有接近一年的笔记等待整理,主要是Linux进程间资源共享,包括变量、锁、函数,很多笔记中没有记录引用链接,这是个不好的习惯。

Linux内核中是不存在线程的概念的,一切可执行单元都是进程。所谓多线程,其实是一组可以共享内存的进程,即“轻量级进程”。所以应该只要实现内存共享,就能让进程之间互相访问。同一个进程的多个线程天然具有上面的特性。

变量共享

master.c

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/shm.h>
#include <sys/ipc.h>

int main()
{
    // 创建(获取)共享内存
    int shmid = shmget(1228, sizeof(int), IPC_CREAT);
    assert(shmid != -1);
    // 共享内存映射到进程地址空间
    void* shmptr = shmat(shmid, 0, 0);
    assert(shmptr != (void*)(-1));
    // 设置一个随机数
    int* num = shmptr;
    srand(time(0));
    int val = rand();
    (*num) = val;
    printf("set %d\n", val);
    return 0;
}

slave.c

#include <stdio.h>
#include <assert.h>
#include <sys/shm.h>
#include <sys/ipc.h>

int main()
{
    // 创建(获取)共享内存
    int shmid = shmget(1228, sizeof(int), IPC_CREAT);
    assert(shmid != -1);
    // 共享内存映射到进程地址空间
    void* shmptr = shmat(shmid, 0, 0);
    assert(shmptr != (void*)(-1));
    // 获取随机数
    int* num = shmptr;
    int val = (*num);
    printf("get %d\n", val);
    return 0;
}

运行结果:

[root workspace]#ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

[root workspace]#
[root workspace]#./master
set 1003187732
[root workspace]#./slave
get 1003187732
[root workspace]#ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x000004cc 0          root       0          4          0

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

[root workspace]#

执行./master后,使用ipcs命令可以看到共享内存key 4cc(10进制1228),然后多次运行可以看到slave每次取到的值都是master设置的随机值。

锁共享

并发访问不仅需要能够相互访问数据,而且往往需要同步、互斥操作。这些东西在多线程情况下基本都可以使用pthread库搞定。pthread库的各种锁,都有相关的flag可以设置是否进程间共享。以信号量为例,semaphore的初始化函数原型如下:

int sem_init(sem_t *sem, int pshared, unsigned int value);

第二个参数pshared就是用来控制是否能跨进程使用,pthread_spin_init函数第二个参数也是pshared,当然我们设置了锁可以跨进程还不够,还需要多个进程能够访问到所,所以自然而然地想到把锁放在共享内存。
master.c

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <semaphore.h>

int main()
{
    // 创建(获取)共享内存
    int shmid = shmget(1229, sizeof(sem_t), IPC_CREAT);
    assert(shmid != -1);
    // 共享内存映射到进程地址空间
    void* shmptr = shmat(shmid, 0, 0);
    assert(shmptr != (void*)(-1));
    sem_t* sem = shmptr;
    // 初始化信号量,设置为跨进程同步
    int ret = sem_init(sem, 1, 0);
    assert(ret == 0);
    while(1)
    {
        ret = sem_post(sem);
        assert(ret == 0);
        sleep(1);
    }
    return 0;
}

slave.c

#include <time.h>
#include <stdio.h>
#include <assert.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <semaphore.h>

int main()
{
    // 创建(获取)共享内存
    int shmid = shmget(1229, sizeof(sem_t), IPC_CREAT);
    assert(shmid != -1);
    // 共享内存映射到进程地址空间
    void* shmptr = shmat(shmid, 0, 0);
    assert(shmptr != (void*)(-1));
    sem_t* sem = shmptr;
    while(1)
    {
        int ret = sem_wait(sem);
        assert(ret == 0);
        printf("%lu\n", time(0));
    }
    return 0;
}

这个例子运行需要开启三个终端,一个运行./master,另外两个运行./slave,两个终端交替输出时间。
运行结果:
终端二:

[root workspace]#./slave
1569146330
1569146330
1569146330
1569146330
1569146330
1569146331
1569146332
1569146333
1569146334
1569146335
1569146337
1569146339
1569146341
1569146343
1569146345
1569146347
1569146349
1569146351
1569146353

终端三:

[root workspace]#./slave
1569146336
1569146338
1569146340
1569146342
1569146344
1569146346
1569146348
1569146350
1569146352
1569146354
1569146355
1569146356
1569146357
1569146358

函数共享

函数的本质是一串连续的机器码,可以把这个连续的机器码看作是一段数据,我们可以把函数的机器码复制到共享内存上,另一个进程去调用该函数。
master.c

#include <assert.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>

#define MEM_SIZE 4096

int add(int a, int b)
{
    return a + b;
}

int main()
{
    // 创建(获取)共享内存
    int shmid = shmget(1230, MEM_SIZE, IPC_CREAT);
    assert(shmid != -1);
    // 共享内存映射到进程地址空间
    void* shmptr = shmat(shmid, 0, 0);
    assert(shmptr != (void*)(-1));
    // 确保函数addr在函数main之前
    assert((void*)add < (void*)main);
    // len的长度肯定大于等于add函数的长度
    size_t len = (void*)main - (void*)add;
    // 确保共享内存放得下add的机器码
    assert(len <= MEM_SIZE);
    // 把add拷贝到共享内存上去
    memcpy(shmptr, add, len);
    return 0;
}

slave.c

#include <stdio.h>
#include <assert.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/mman.h>

#define MEM_SIZE 4096

int main()
{
    // 创建(获取)共享内存
    int shmid = shmget(1230, MEM_SIZE, IPC_CREAT);
    assert(shmid != -1);
    // 共享内存映射到进程地址空间
    void* shmptr = shmat(shmid, 0, 0);
    assert(shmptr != (void*)(-1));
    // 修改共享内存权限为可读可写可执行
    int ret = mprotect(shmptr, MEM_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC);
    assert(ret == 0);
    // 得到函数指针
    int (*f)(int, int)= shmptr;
    // 执行函数
    printf("%d\n", f(1, 2));
    // 执行函数
    printf("%d\n", f(7, 8));
    return 0;
}

运行结果:

[root workspace]#gcc -g -o master thread.c
[root workspace]#
[root workspace]#./master
[root workspace]#gcc -g -o slave thread.c
[root workspace]#
[root workspace]#./slave
3
15