<深入linux内核架构>--第五章 锁与进程间通信
来源:互联网 发布:淘宝买家账号钻石级别 编辑:程序博客网 时间:2024/06/10 01:29
内容简介:主要讲解了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 10000BOOL_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. 管道和套接字
说明:此部分内容介绍更加简洁,实际上内容很多,读者需要在学习或工作中慢慢体会。
- <深入linux内核架构>--第五章 锁与进程间通信
- 深入linux内核架构-第二章-进程管理和调度
- 深入理解Linux内核-第五章笔记
- 读书笔记--深入分析Linux内核源码-第五章进程调度
- 高级Linux程序设计第五章:进程间通信
- 高级Linux程序设计第五章:进程间通信
- 高级Linux程序设计第五章:进程间通信
- 深入Linux内核架构之进程篇一
- 深入linux内核架构读书笔记-进程管理和调度
- 《深入理解Linux内核》--第五章 内核同步:读书笔记
- 进程与进程,进程与内核间通信(netlink)
- 深入理解Linux进程间通信
- <深入linux内核架构>--第七章 模块
- Linux内核学习8:进程间通信(1)——基本概念与管道通信
- 深入linux设备驱动程序内核机制(第五章) 读书笔记
- Linux进程、线程与进程间通信
- Linux C 进程与进程间通信
- linux 内核进程与用户进程的通信 方法一 使用sockopt与内核交换数据
- 快速搭建视频直播平台
- 《白话深度学习与Tensorflow》学习笔记(7)RBM限制玻尔兹曼机
- 逻辑电路,零和一的实验
- Java 视频网盘分享
- PAT-1010. Radix (25)
- <深入linux内核架构>--第五章 锁与进程间通信
- LeetCode解题-#20-Valid Parentheses
- K Inverse Pairs Array
- 阿里云linux服务器如何挂载数据盘?
- 视图绘制三部曲之onMeasure()源码最简解析 带你轻松领略源代码之美
- Visual Studio交叉编译器提供对ARM的支持
- 大叔公开课~微服务与持久集成
- 这应该是目前最快速有效的ASP.NET Core学习方式(视频)
- LeetCode377. Combination Sum IV