<深入linux内核架构>--第五章 锁与进程间通信

内容简介:主要讲解了Linux各个独立进程间(或线程间)相互通信的机制(主要是System V机制),由于涉及到进程间资源共享,引入资源保护问题,也就是Linux的锁。

5.1 控制机制

         首先通过一个竞态条件的例子引入需要对锁的需求,然后介绍了Linux系统锁控制机制:临界区。一种典型的使用临界区机制的锁方法:信号量。

5.2 内核锁机制

         讲解了Linux内核态几种锁机制:

1.      原子操作:

atomic_t

2.      自旋锁:

spinlock_t

3.      信号量:

struct semaphore

4.      读写锁:

rwlock_t

         API:write_lock、read_lock、read_trylock等;

5.      RCU:read-copy-update。该机制记录了指向共享数据结构的指针的所有使用者。在数据将要改变时(写操作),先创建一个副本,在副本中修改。在所有的读访问的使用者结束对旧副本的读取后,指针替换为指向新的、修改后副本的指针。

rcu_dereference、rcu_read_lock、rcu_read_unlock

6.      内存与优化屏障

主要是阻止编译器的指令重排吧。比如读屏障:保证屏障之后发出的任何读操作执行之前,屏障之前发出的所有读操作已经执行完成(就是说屏障之后读取的内容肯定是最新的);

mb、rmb、wmb、smp_mb

barrier

7.      大内核锁:已废弃;

8.      互斥量

1)      经典互斥量:struct mutex

2)      实时信号量:struct rt_mutex,支持优先级反转;

9.      近似的per-CPU计数器

思想:计数器的准确值存储在内存中某处,准确值所在内存位置之后是一个数组,每个数组项对应于系统中的一个CPU。如果某个特定于CPU的数组元素修改后的绝对值超过某个阈值,内核确保通过适当的锁机制修改计数器的准确值。

1.3  System V进程间通信(用户态)

1.      信号量

相关操作:semget、semop、semctl

2.      共享内存

3.      消息队列

此部分内容讲解比较基础,具体的内容可以参阅《UNIX网络通信—进程间通信》。笔者通过以下例程简单介绍信号量的用法。

 ipcd.c文件:

#include <stdio.h>
#include <getopt.h>
#include <pthread.h>
#include "../../../PUBILIC/include/basetype.h"
#include "ipc_sem.h"

#define IPC_THREAD_NUM  10
#define IPC_MAX_ADD_NUM  10000

BOOL_T g_bIsHaveLock = BOOL_FALSE;
UINT g_uiTestUint = 0;
pthread_t g_astThreadID[IPC_THREAD_NUM];
    
ULONG ipc_ParseParam(INT argc, CHAR **argv)
{
    ULONG ulRet = ERROR_SUCCESS;
    INT iOption = 0;
    while (-1 != (iOption = getopt(argc, argv, "l:")))
    {
        switch (iOption)
        {
            case 'l':
            {
                g_bIsHaveLock = BOOL_TRUE;
                break;
            }
            default:
            {
                ulRet = ERROR_FAILED;
                break;
            }
        }
    }
    
    return ulRet;
}

VOID *ipc_thread_Func(VOID *pParam)
{
    UINT uiIndex;
    
    pthread_t stSelf;
    stSelf = pthread_self();
    if (BOOL_TRUE  == g_bIsHaveLock)
    {
        IPC_Sem_Down();
    }
    for (uiIndex = 0; uiIndex < IPC_MAX_ADD_NUM; uiIndex++)
    {
        g_uiTestUint++;
    }

    /* 加完之后再打印值 */
    printf("This is thread(%lu): UINT(%u)\n", stSelf, g_uiTestUint);
    if (BOOL_TRUE  == g_bIsHaveLock)
    {
        IPC_Sem_Up();
    }

    return NULL;
}
ULONG ipc_Init()
{
    ULONG ulRet = ERROR_SUCCESS;
    UINT uiIndex;
    
    ulRet = IPC_Sem_Init();
    if (ERROR_SUCCESS != ulRet)
    {
        return ulRet;
    }
    /* 创建10个线程同时对全局变量进行操作 */
    for (uiIndex = 0; uiIndex < IPC_THREAD_NUM; uiIndex++)
    {
        pthread_create(&g_astThreadID[uiIndex], NULL, ipc_thread_Func, NULL);
    }

    return ulRet;
}

VOID ipc_Fini()
{
    UINT uiIndex;
    for (uiIndex = 0; uiIndex < IPC_THREAD_NUM; uiIndex++)
    {
        pthread_join(g_astThreadID[uiIndex], NULL);
    }

    IPC_Sem_Fini();
    return;
}

INT main(INT argc, CHAR **argv)
{
    ULONG ulRet = ERROR_SUCCESS;

    /* 解析参数 */
    ulRet = ipc_ParseParam(argc, argv);
    if (ERROR_SUCCESS != ERROR_SUCCESS)
    {
        return -1;
    }
    
    ulRet = ipc_Init();
    if (ERROR_SUCCESS != ulRet)
    {
        ipc_Fini();
    }

    ipc_Fini();

    return 0;
}

ipc_sem.c文件:

#include <stdio.h>
#include <sys/sem.h>
#include "../../../PUBILIC/include/basetype.h"
#include "ipc_sem.h"

struct sembuf g_stopDown[1] = {0, -1, 0};
struct sembuf g_stopUp[1] = {0, 1, 0};
INT g_iSemId = -1;

ULONG IPC_Sem_Init()
{
    INT iSemId = -1;
    INT iRet;
    ULONG ulRet = ERROR_FAILED;
    /* 首先打开信号量 */
    iSemId = semget(IPC_SEM_LEY, 0, IPC_CREAT | 0666);
    if (iSemId < 0)
    {
        /* 打开失败再创建 */
        printf("Sem(%ld) does not exist, then create it.\n", IPC_SEM_LEY);
        iSemId = semget(IPC_SEM_LEY, 1, IPC_CREAT | 0666);
        if (iSemId < 0)
        {
            printf("Create sem(%ld) failed.", IPC_SEM_LEY);
            return ERROR_FAILED;
        }
    }

    /* 设置信号量值 */
    iRet = semctl(iSemId, 0, SETVAL, 1);
    if (iRet == 0)
    {
        g_iSemId = iSemId;
        ulRet = ERROR_SUCCESS;
    }
    return ulRet;
}

VOID IPC_Sem_Up()
{
    semop(g_iSemId, &g_stopUp[0], 1);
    return;
}
VOID IPC_Sem_Down()
{
    semop(g_iSemId, &g_stopDown[0], 1);
    return;
}

VOID IPC_Sem_Fini()
{
    /* 删除信号量 */
    if (g_iSemId > 0)
    {
        semctl(g_iSemId, 0, IPC_RMID);
    }
    return;
}

程序讲解:

1)      Main函数进来先解析参数:是否有锁,解析参数用于设置全局变量g_bIsHaveLock;

2)      ipc_Init()函数中的IPC_Sem_Init()函数打开或创建了一个信号量。之后创建了10个线程同时对全局变量IPC_Sem_Init进行10000次++操作,如果没有竞态条件发生,最后的结果应该是10 * 10000 = 100000;

3)      线程函数进去后先判断有没有锁:有锁则进行down操作,获取信号量,这样就不受其他线程干扰;没锁则直接进行变量++操作;

4)      通过以不同的命令行参数启动进程,可以看到有锁和没锁情况下全局变量的值。

程序效果:

1)      无锁启动

 

2)      有锁启动


补充说明:

1.      程序代码没贴全,如UINT、ULONG等类型申明都放在我的basetype.h头文件中,由于经常用到,所以用一个单独的头文件申明;

2.      Ipc_sem.h头文件没有贴,也就是一些函数申明;

总之:

         读者应该理解信号量的精髓和使用方法,而不是简单的“抄”代码。


1.4  其他IPC机制

1.      信号

2.      管道和套接字

说明:此部分内容介绍更加简洁,实际上内容很多,读者需要在学习或工作中慢慢体会。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值