QNX系统常用指令、多线程、文件系统、I2C和编译选项等

【生活经历分享】华师国培 华师伴学 合同都是坑 消费者付款后无法退款
和华师国培签合同需小心,合同中都是保护华师的条款,没有保护消费者的条款。
收到钱,就算你因对培训质量不满意,也不能退款。因合同消费者维权肯定十分艰难。
华师伴学的授课方式是看录制的视频,不是真人现场教学。是否是您和孩子想要的学习方式?
各位打算报名的,交费要谨慎!
其他人在小红书上发的,转:
深圳市华师国培教育科技有限公司,黑心机构,大家擦亮眼睛,别被骗了,消费欺诈,虚假承诺,签合同各种坑,收到钱了不履行承诺不退款,乱扣费,维权艰难! - 小红书
【工作要用到的技术,之前记录在云笔记中,因时间太久已经不记得出处。如果有问题请联系我,会及时配合处理】

本文重点内容目录

序、QNX常用指令
一、QNX文件系统
二、简介
三、QNX环境下多线程编程
四、05-SA8155 QNX I2C框架及代码分析
五、QNX编译选项
六、QNX Neutrino 进程间通信编程之Shared Memory
 
 
常用指令
hogs: 列出最占用 CPU/RAM 的进程,可以统计内存占用详细情况【可单个进程】# 显示进程 39379114 及各个核的占用情况,每 1s 刷新一次 hogs -s 1 39379114 # 统计 shared 和 private 内存映射,并按内存占用排序 hogs -msp -Sm # 显示 CPU/内存 占用超过 10% 的进程 hogs -% 10c
hogs -% 10m
MSEC: 自上次结果以来,该进程运行的 ms 数 PIDS: 本次迭代中,进程运行时间的占所有其他进程运行时间的百分比 SYS: 本次迭代中,进程运行时间的占迭代时间的百分比 MEMORY: 进程内存用量,取决于 -m 参数
 
pidin: 显示进程信息
# 显示 MyProcess 进程的 pid、tid、线程名及完整路径 pidin -fabhn -P MyProcess # 显示系统信息(CPU、内存、进程、线程) pidin info # 显示 MyProcess 进程库信息 pidin -P MyProcess libs
查看系统进程:
# pidin | grep i2c
40982 1 bin/i2c_service 10r RECEIVE 8
40982 2 bin/i2c_service 15r RECEIVE 2
40982 3 bin/i2c_service 21r SEM fff808a056320f94
40982 4 bin/i2c_service 41r INTR
40982 5 bin/i2c_service 10r RECEIVE 3
40982 6 bin/i2c_service 41r INTR
40982 7 bin/i2c_service 10r RECEIVE 6
40982 8 bin/i2c_service 41r INTR
40982 9 bin/i2c_service 10r RECEIVE 11
查看memory
pidin mem
pidin -P <进程名> 查看某个进程内存使用情况
查看系统页信息
pidin syspage=asinfo
 # 显示线程名、显示资源消耗最高的 40 个线程 top -tz 40
一、QNX文件系统

https://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.sys_arch/topic/fsys_QNX6_filesystem.html
Power-Safe filesystem[QNX6 filesystem]
Power-Safe 的 QNX6 文件系统,此文件系统号称使用了 Copy-On-Write(写时拷贝,或者写前拷贝) 技术【此技术使用很广泛!——详细见后续说明】能够很好的预防在系统崩溃时发生的文件损坏问题。但 QNX6 文件系统不能解决 NAND Flash 的读写寿命问题。
由[fs-qnx6.so]提供的一种可靠文件系统,它能够在电源异常时不丢失或损坏数据。
Power-Safe filesystem不会覆盖现有的数据,它通过copy-on-write(COW)机制分配一个未使用的块进行操作。只有当所有操作完成时采用这个新块替代现有数据。系统中所有操作都是通过COW来操作的,包括元数据和用户数据。
COW方法的缺点:
  • 任何改变数据的操作都回引起双倍的数据块被拷贝和修改(降低源数据卷的写性能: 当修改源数据时,会发生一次读和两次写,共三次操作。如果主机写入数据频繁,那么这种方式将非常消耗I/O)。
  • 当取得快照的时候,文件系统必须在提交superblock之前将所有块写入磁盘。
  • 无法得到完整的物理副本。快照卷仅仅保存了源数据卷的部分原始数据。
  • 拷贝到快照卷中的数据量超过了保留空间,快照就将失效。
COW方法的优点:
  • COW 在进行快照操作之前,不会占用任何的存储资源,也不会影响系统性能。
  • 写入操作时与顺便无关。
  • 一个新块可以被分配到任何空闲,连续的磁盘上。
Encryption
You can encrypt all or parts of a Power-Safe filesystem in order to protect its contents.
Embedded transaction filesystem(ETFS)
NAND flash的只读数据在访问100000次后也容易发生异常。QNX为了解决这个问题设计了ETFS文件系统,此文件系统会跟踪NAND Flash每个数据块的读写次数,并提前作出处理。并且此系统能够延长NAND Flash的使用寿命。
ETFS为了嵌入式的固态存储设备(特别是NAND FLASH)实现的一种高可靠性文件系统。ETFS是个完全基于事务(transaction)的文件系统。每个针对用户数据或文件系统元数据的写操作都是基于transaction的,成功后反映数据,否则全部回滚。transaction中不会覆盖现存的数据,一个针对文件或目录的写操作都会写在一个未使用过的区域中。这时如果其中发生错误也不会影响即存的数据。
一些文件系统(如:日志文件系统,具有故障恢复能力的文件系统)也是不会覆盖现有的数据。但是ETFS做到了极致,它将所有事情转化为 transactions log。ETFS是构建在对设备上的transactions log的审查上。这种审查只是发生在启动时,为了加快启动时间牺牲了一定的稳定性,只是针对一部分数据作CRC检查。
事务(transaction)与设备中的位置无关,并且可以以任何顺序操作。可以从设备中读取一个transaction,可以将它们以不同的顺序写道另一个设备中。在大规模数据中,因为坏点可能出现在任何地方,所以这点显得尤其重要。这种机制也能够很好的支持NAND flash内存。NAND flash通过factory-marked的坏点可能出现在任何地方。

 

1、Inside a transaction
  每个都包含一个信息头(Header)和一个数据块(data)。信息头中包括:
  必须属性(与设备无关)
  ・FID:一个唯一的文件ID,来识别这个transaction。
  ・Offset:文件中数据块的偏移。
  ・Size:数据块的大小
  ・Sequence:一个递增的数字,以保证时间顺序
  可选属性(与设备相关)
  ・CRCs:完整性检查数据(支持NAND、NOR、SRAM)
  ・ECCs:错误纠正(支持NAND)
  ・Other:预留位
2、Reliability features
ETFS是为了即使电源故障(甚至在flash写操作中)或快擦除下也能正常使用的文件。他支持:
  ・dynamic wear-leveling(动态磨损均衡):Flash内存只容许固定次数(至少100,000)的擦除周期。ETFS会跟踪每个块的擦写次数。当选择使用一个块的时候,ETFS会试图延续这个擦写周期,这可能戏剧性的增加它的使用寿命。可能是一个过几天就要坏的快,在ETFS下可能还可以正常使用40年(感觉在吹牛!)。
  ・static wear-leveling:文件系统中通常包含大量的静态文件,它们只会被读取而不会被写入。而被这些文件占据的区域也就不会被擦写。如果一个flash中的多数区域都是这种静态文件,就会导致剩余包含动态数据的分区增加了发生error的几率。 ETFS为了解决这种问题会将这些静态文件拷贝到一些过载的(over-worked)区域中。这即可以让那些过载的区域能够休息一下,以能够让那些很少擦写的静态区域补充到过载的擦写区域中。(有点好心办坏事的感觉)
  ・CRC error detection:每个Tansaction都被循环冗余码(Cyclic redundancy check:CRC)保护中,它能够在启动时快速判断是否是正确的数据,并且能够回滚错误或未完成的数据。CRC能够判断出电源异常时可能发生的多bit异常。
  ・ECC error correction:当有CRC异常时,ETFS能够通过ECC码去试图纠正、恢复数据。这个适合于NAND Flash内存,它可能会在普通运用时发生单bit的异常。ECC异常通常意味着Flash块可能变得脆弱了,这是ETFS会针对这个块进行刷新(Refresh)操作,将数据拷贝到新的块中,并擦除这个脆弱的块。
  ・Read degradation monitoring with automatic refresh(具有自动刷新功能的读取降级监测):每一次针对NAND Flash的读取操作都会消弱数据bit。多数设备在丢失数据之前支持的100,000的读取。ECC能够恢复单bit的异常,但对于大规模的异常却无能为力。ETFS能够跟踪读取次数,并在这个极限值100,000次达到之前去刷新。
  ・Transaction rollback:ETFS启动时会检查所有事务,并回滚或丢弃损坏和不完全的事务。这种回滚是为了处理电源异常而设计,它也支持多次嵌套的异常。ETFS是根据CRC码来判断买个事务的是否正确的。
  ・Atomic file operations:ETFS在设备上实现了一个非常简单的目录结构,它容许在一个flash写操作中作变更。如,移动一个文件或目录到其他目录中,在其他系统需要多步才能完成,而在ETFS只需要一步。
  ・Automatic file defragmentation(自动文件碎片整理):Log-based文件系统经常苦于磁盘碎片的困扰,因为每次更新或写操作都会产生一个新事务。ETFS通过写缓存(write-buffering)来合并一下小规模的写操作为一个大规模的试图减少因为小事务而长生的磁盘碎片。ETFS也会见时每个文件的碎片登记,并且会在后台作磁盘碎片整理。为了能够及时相应用户的访问,后台的碎片整理总是在用户请求后执行。
 
https://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.sys_arch/topic/fsys_ETFS.html
——————————————————————————————————————————
QNX操作系统通过莱茵认证的基础功能安全认证IEC61508SIL3,道路车辆功能安全最高等级ISO 26262 AISL D标准,医疗行业IEC62304及铁路EN50128认证功能安全认证,认证范围包括工具链TCL3认证、Neutrino微内核、APS自适应分区调度、libc、libm和libsupc++库等。
——————————————————————————————————————————
 
COW 相关概念 - https://support.huawei.com/enterprise/zh/doc/EDOC1100196336
  • 数据组织形式
存储系统采用虚拟化存储技术。存储池中创建的LUN【矩阵】由元数据卷(Meta Volume)和数据卷(Data Volume)两部分组成。
    • 元数据卷(Meta Volume):记录LUN中数据组织形式及其属性的卷,结构形式为树形。
    • 数据卷(Data Volume):存放LUN中的实际数据的卷,数据读取和写入都以Extent为单位。
  • 源卷
需要进行快照操作的源数据所在的卷,对用户而言表现形式为源LUN。源LUN包括Meta Volume和Data Volume:
    • Meta Volume:记录源数据在源LUN中的存在位置。
    • Data Volume:记录源LUN中存放的业务数据。
  • 快照卷
对源LUN创建快照后,逻辑上生成的数据副本。对用户而言表现形式为快照LUN。快照LUN包括Snapshot Meta Volume和Snapshot Data Volume:
    • Snapshot Meta Volume:快照元数据卷。每生成一份快照,就会建立一个Snapshot Meta Volume。
    • Snapshot Data Volume:与Snapshot Meta Volume对应的数据卷。记录的是写快照时产生的数据。
  • COW数据空间
快照生成并激活后,存储系统在源LUN所在的存储池中动态划分一部分存储空间,用于保存写前拷贝数据。同一个源LUN对应的所有快照LUN共享同一个COW数据空间。COW数据空间包括COW Meta区域和COW Data区域:
    • COW Meta区域:同一个源LUN下所有快照共用的元数据区。用于存放写源LUN时所有快照LUN所产生的写前拷贝映射项,即写前拷贝数据与其在COW Data区域中存放位置的对应关系。
    • COW Data区域:与COW Meta区域对应的数据空间。用于存放写源LUN时产生的写前拷贝数据。
  • 映射表
映射表用于记录源LUN数据和快照LUN数据的在某个时间点的改变情况以及改变后的存储位置。映射表分为共享映射表和独享映射表:
    • 共享映射表存放在COW Meta区域中,用于记录写源LUN时产生的写前拷贝数据与其在COW Data区域中存放位置的映射关系。
    • 独享映射表存放在每个快照LUN的Snapshot Meta Volume中,用于记录写快照LUN所产生的数据与其在快照LUN中存放位置的映射关系。
COW
COW被称为即写即拷快照技术或写时拷贝快照技术,当主机将数据第一次写入到存储某个位置时,首先将原有的位置的内容读取,写到COW数据空间,然后将新数据写入到存储设备中。而下次主机针对这一位置的写操作将不再执行写时拷贝。这种实现方式在首次写入时需要完成一个读操作(读取源位置的数据),两个写操作(写原位置和写快照预留空间)。如果写入频繁,那么这种方式将非常消耗I/O操作。因此可以推断,当某个LUN上的I/O多数以读操作为主,写操作较少时,可以采用COW快照技术。另外,如果一个应用易出现写入热点,即只针对某个有限范围内容的数据进行写操作,那么也可以采用COW快照技术,因为同一份数据的多次写操作只会出现一次写时拷贝。
快照创建并激活后,会生成与源卷一致的数据副本。存储系统在源卷中划分出COW数据空间并自动生成快照卷。此时,由于快照刚创建成功,对源卷还没有写操作,COW Meta区域和COW Data区域中均没有记录。同时对快照卷也没有写操作,Snapshot Meta Volume和Snapshot Data Volume中也没有记录。快照初始状态示意图如下:
图1-1 快照初始状态

写源数据块
快照激活后,当应用服务器对源卷有数据写入请求时,存储系统不会立即写入新数据。存储系统利用写前拷贝机制先将写前拷贝数据拷贝到COW数据空间中,并修改映射表中的映射关系,然后将新数据写入源卷。应用服务器分别在Time1之后下发写源卷的请求:
  1. “Data1”修改为“DataX”;
  2. 利用写前拷贝机制将“Data1”拷贝到COW数据空间中;
  3. 更新映射表中的映射关系,“Data1”的存放位置变更为COW数据空间中的“g0”;
  4. 将“DataX”写入源卷,完成数据更新。
图1-2 写源卷IO流程图

COW 在创建快照时,并不会发生物理的数据拷贝动作,仅是拷贝了原始数据所在的源数据块的物理位置元数据(指针信息)。因此,COW 快照创建非常快,可以瞬间完成。在创建了快照之后,快照软件会监控跟踪原始数据的变化(即对源数据块的写操作),一旦源数据块中的原始数据被改写,则会将源数据块上的数据拷贝到新数据块中,然后将新数据写入到源数据块中覆盖原始数据。其中所有的源数据块就组成了所谓的源数据卷,而新数据块组成了快照卷。因为快照卷只保留发生过变化的数据块,与数据卷相比要小的多。另外 COW 有一个很明显的缺点,就是会降低源数据卷的写性能,因为每次改写新数据,实际上都进行了两次写操作。
写快照卷
快照激活后,应用服务器可以对快照卷进行读写操作。应用服务器下发的写请求后,数据将直接写入快照卷,并在映射表(独享部分)中记录数据在快照卷中的存放位置。
  1. 应用服务器在Time2下发写快照卷的请求:写入数据“Data a”,并将该数据直接写入快照卷中;
  2. 在映射表(独享部分)中记录“Data a”在快照卷中的存放位置“g'0”。
图1-3 写快照卷IO流程图

读快照数据(快照卷已写入)
如果需要访问某个时间点的快照数据,对没有改变过的块直接从数据卷读取;对已经改变并被复制的块则从快照空间读取。从快照被创建那一刻开始,每个快照都会跟踪记录描述块改变的元数据信息。
当应用服务器中在快照卷中写入数据Data a。此时,应用服务器读取快照数据的示意图如下图所示。
  1. 应用服务器下发读快照请求;
  2. 通过映射表(独享部分)确定快照数据的存放位置;
  3. 应用服务器读取到的快照数据为“Data a”。
图1-4 读快照卷(快照卷已写入)

读快照数据(快照卷未写入)
当应用服务器只在源卷中写入数据,没有在快照卷中写入数据。此时,应用服务器读取快照数据的示意图如下图所示。
  1. 应用服务器下发读快照请求;
  2. 通过映射表(共享部分)确定快照数据的存放位置;
  3. 应用服务器读取到的快照数据为“Data 0”、“Data 1”、“Data 2”、“Data 3”。
图1-5 读快照卷(快照卷未写入)

二、QNX简介
QNX操作系统是由加拿大QSSL公司(QNX Software System Ltd.)开发的分布式实时操作系统。
由于QNX在设计实现时,遵循了POXIS 1003.1标准,使得它在许多功能上与UNIX操作系统极为相似,既支持多个用户同时访问,也支持多个任务同时执行。因此,它是一个多任务、多用户的操作系统。

QNX之所以那么小,是因为作为微内核,它将驱动、系统服务、应用、协议栈、文件系统等都剔除在内核外,以可加载模块的形式运行,QNX内核只完成最基础的服务,如时钟同步、进程间通信、进程调度等。Linux作为宏内核,包含了许多服务,如文件系统、网络、驱动程序等。
 
QNX内核架构:

  30天评估版:

http://www.qnx.com/products/evaluation/
 完整的命令手册:
http://www.qnx.com/developers/docs/7.0.0/#com.qnx.doc.neutrino.utilities/topic/about.html
QNX下的帮助命令是use,如:#use ls 或者 #use chmod
 https://zhuanlan.zhihu.com/p/46528076
 QNX Momentics IDE 4.6只能试用30天,解决此问题的方法有两种:一是记录下来其安装时间,在快到期的时候回调win7系统的时间,一旦回调不及时就会过期只能使用第二种方法;二是重装win7系统,重新安装IDE。且任何系统都推荐断开外网连接,防止系统升级或后台使用网络占用开发资源。
因此利用虚拟机学习qnx个人认为是一种好的方法,重装虚拟机对自己的机器并没有什么影响,开发环境相对来说好建立一点,推荐双虚拟系统。

三、QNX环境下多线程编程
在早期的QNX版本如QNX4中,对于线程的支持是比较弱的,在当时的条件下,处理大型、复杂的并发多任务问题时,常常将问题分解为多个进程以降低问题的复杂性。而且QNX提供了与UNIX类似的进程间通讯IPC手段如消息、代理、信号灯等,功能也相对比较成熟、完善。1999年以后 QNX软件公司推出了QNX/Neutrino实时操作系统的Neutrino2.0、Neutrino6.0增加了对于POSIX线程的支持,标准的API不但使它易于扩展,而且也使得编写多线程程序变得容易。由于线程具上下文较轻、切换较快、在创建多个线程时系统的开销比较小、通讯手段灵活多样、共享资源丰富等优点,在处理大型并发多任务问题时多线程有了明显的优势。QNX是抢先式多任务系统,这种系统决定了多个线程在访问共享资源时线程执行的次序变得不可预期,所以线程间的同步就显得极为重要。QNX提供了多种同步机制以保证多线程程序的安全、可靠。
 
1 QNX多线程库函数简介
  QNX与LINUX不同,没有单独的线程库,与线程有关的API是作为C语言库函数的一部分使用的,头文件是,同样方便地提供线程的创建、终止和同步等功能。QNX不仅在C语言库函数中提供了符合POSIX1003.1c标准的与线程相关的API,而且还提供了很多POSIX标准没有的扩展功能,使得多线程编程变得更加容易。
1.1线程的创建、取消和终止
  1.11 线程的创建
  int pthread_create( pthread t* thread, const pthread attr t* attr, void* (*start routine)(void* ), void* arg ):被创建的线程执行start routine() 函数,thread返回创建的线程描述符,而attr是创建线程时设置的线程属性,arg可以作为任意类型的参数传给start routine()函数。QNX对创建线程前需要设置的线程属性扩展如下:可以禁止一个线程的取消(终止操作);可以设置一个线程的取消类型;可以指定当一个线程接到信号时,它如何操作。
  1.12 线程的取消
  int pthread_cancel(pthread_t thread):函数取消由thread指定的线程,如果成功则返回0,否则为非0,成功并不意味着thread会终止,要视取消的状态和类型而定。
  1.13 线程的终止
  void pthread exit( void* value ptr ): 当一个线程在执行了start routine()函数后返回时,系统自动隐式调用pthread exit()使其退出,start routine()的返回值,作为线程的退出状态。在一个线程中也可以显式调用pthread exit()退出,对于单线程进程而言,调用pthread exit()与调用exit(0)是等效的。
 
1.2 线程的常用控制函数
  pthread_self():返回线程描述符,pthread_create()返回值相同。
  pthread_equal():此函数功能为比较两个线程的描述符,不管线程描述符是否合法。如果返回值为非零说明两个线程是同一线程,为零说明两个线程不是同一线程。
  pthread_join():等待一个线程的终止,调用pthread_join()的线程将被挂起,直到目标线程终止。一个线程仅允许唯一的线程使用pthread_join()等待它的终止,并且被等待的线程应该处于非DETACHED状态。QNX也提供了非POSIX的 pthread_timedjoin(),不同之处是线程在给定时间里没有被join时,此函数会返回一个错误信息。
 pthread_detach():将一给定线程分离,当一个出于分离状态的线程终止时,线程拥有的所有系统资源将被立即释放。
 
2 QNX线程的互斥和同步机制
  多线程程序中线程之间的关系是复杂的,为了防止竞争条件和数据被破坏的情况发生,QNX提供了多种互斥和同步机制,包括互斥体、条件变量、信号灯、屏障、读/写锁、sleepon锁等,其中最主要的是互斥体和条件变量,其余的同步机制都是由他们构造而成的,当然你也可以根据自己的要求构建自己的同步机制。
  互斥体——QNX使用了互斥体来实现互斥操作,在初始化互斥体后,将给定的代码或变量的前后进行加锁、解锁操作,线程在访问之前要先得到互斥体,这样就可以保证只有一个线程能访问到代码或变量,而其余的线程会处于阻塞状态直到互斥体被释放。这种机制保证了线程对资源访问的互斥性,达到了对代码或变量的保护。
  条件变量——QNX的条件变量用来同步线程,所有线程都会等待一个条件变量可用,当条件满足时,一个线程发出广播或信号来同步所有的线程或某一线程。为了防止多个线程同时申请等待而产生竞争,条件变量通常要与互斥体联合使用。
  信号灯——QNX信号灯也是一种符合POSIX标准的的同步机制,它是由互斥体和条件变量结合一些数据构造而成的,QNX系统将其封装在C语言库函数中,头文件是。它的功能很强大,可以允许多个线程访问同一资源,可以通过设定灯值来限定线程的个数,灯值为一时它就是互斥体。
屏障——POSIX 1003.1j提议的内容,主要由互斥体、条件变量和计数器构造而成。作用是停止某些线程,当所要求的线程数量到达屏障时,所有的线程被允许继续运行。屏障通常被用来确保某些并行算法中的所有合作线程在任何线程可以继续运行以前到达算法中的一个特定点。
读/写锁——POSIX 1003.1j提议的内容,主要由一个互斥体和两个条件变量构造,两个条件变量分别控制读写操作。读/写锁允许多个线程同时读数据,但是禁止任何线程修改正在被其他线程读或修改的数据,也可以让一个线程独占写访问,但此时任何读访问的线程都不能继续,直到读/写锁被释放,所以读/写锁被用来保护经常需要读但是通常不需要修改的信息。
  Sleepon锁——QNX6所独有的一种同步机制,由一个互斥体和一些数据构造而成,与条件变量相似但是用法比较简单。它与条件变量的不同在于当有N个线程阻塞在M个对象的时候,如果用条件变量来同步时需要用到M个条件变量,而sleepon锁为每个线程自动分配条件变量,只需要N个条件变量,使用合适可以节约系统资源。
 
3 多线程程序的设计分析与基本结构
  3.1多线程程序的设计分析
  1) 确定完成任务所需的最少线程个数。
  多余的线程只会使程序的复杂性增加,出错的可能性也随之增加。设计程序时要遵循简单、高效、安全的原则,如果用单线程能够很好的完成任务,那么一定不要用多线程。
  2) 分析多线程需要共享的数据。
  在多线程程序中常常需要共享一些数据,通常是一些全局变量,如果数据量很大可能需要开辟共享内存区。
  3) 根据共享数据的特点选择需要的保护机制。
  多个线程需要写操作的变量可以用互斥体保护,经常需要读操作而很少进行写操作的可以用读/写锁保护等。
  4) 分析工作线程需要访问资源。
  工作线程可能需要访问硬件,等待硬件响应、可能需要访问某一数据库、也可能不访问任何资源只是进行一些计算等等。这时需要考虑相应的同步机制,可以用条件变量结合互斥体,也可以用更为简单的sleepon锁。
  5) 进行线程的清理工作。
  线程完成工作后可能会自动退出,也可能会阻塞在某处,甚至工作线程还没完成工作的时候主线程已经退出,造成整个进程的结束,使程序失败。有多种方法可以完成线程的清理工作,可以让主线程调用pthread_join()函数清理工作线程,可以用屏障同步机制清理。
  3.2多线程程序的基本结构
  多线程编程的结构有很多种,但基本的编程结构总结起来有三种:流水线结构、工作组结构、客户端/服务器结构。这三种结构可以以任意方式组合,来满足实际工程的需求。
  1) 流水线结构
  在流水线结构中,需要处理的“数据”串行地被一组线程顺序处理,每个线程依次在每个数据元素上执行一个特定的操作,完成操作后将结果传递给流水线中的下一个线程。如图1所示。   

   2) 工作组结构

  在工作组结构中,数据有一组线程分别独立地处理,每个线程处理不同的部分。由于所有的工作线程在不同的数据部分上执行相同的操作,这种模式通常被称为SIMD(单指令多数据流)并行处理。但是工作组中的线程可以不使用SIMD模型,他们可以在不同的数据上执行不同的操作。工作组结构是多线程程序应用较多的一种结构。如图2所示

   3) 客户端/服务器结构

  在客户/服务器结构中,客户请求服务器对一组数据执行某个操作。客户端独立地执行操作,而客户端或者等待服务器执行的结果,或者并行执行另外的任务,在后面需要时在查找结果。这种结构又是一种对某些公共资源同步管理的简单方式。如图3

 4 QNX Neutrino内核对于线程功能的扩展

  具有Neutrino内核的QNX6操作系统对线程的功能进行了扩展,提供了一些POSIX标准没有提供的功能。
  1) POSIX标准规定使用互斥体的线程必须在同一进程内,作为扩展QNX支持在不同进程中的线程使用互斥体。如果在两个进程间创建一块共享内存,并在内存中初始化一个互斥体,那么两个进程之间的线程可以用这个互斥体来进行同步操作,这是POSIX做不到的。
  2) QNX操作系统还提出了一种独特的“线程池”概念。当程序需要很多线程同时工作时,利用“线程池”可以将线程的个数限定在一定的范围内。“高水位”、“低水位”的概念分别对应着程序中的最大线程数和最小线程数。当程序中线程数目小于“低水位”时,“线程池”会自动创建新的线程进行工作,当线程数目大于“高水位”时,“线程池”会“清除”多于的线程,以防止溢出。这样程序将始终保持着一定数量的线程在工作,“线程池”特别适用客户端/服务器结构,可以很好地保护服务器的资源。QNX提供了专门的程序库来管理“线程池”头文件是,相应的API主要有:thread_pool_create(),用于建立一个线程池,thread_pool_destroy()程序运行结束后用它来清除线程池,thread_pool_start()用来启动一个线程池。
 
5 QNX系统下实用编译方法
  笔者编制了QNX环境下的通用Makefile,用于编译多线程程序,当然也适用于单线程程序的编译,而这个Makefile稍加改动便可以用于LINUX/UNIX系统中,笔者在RedHat Linux7下试验通过。用法,首先将此Makefile 和所要编译的c/c++程序(支持多个c/c++程序)/头文件放置于一个目录中,在终端上键入make,此Makefile将自动把所有相关源代码连接编译成名为go的可执行文件,要运行编译好的程序,只需在终端上键入./go便可。在终端上键入make clean将把所有的编译产生的临时文件删除,只留下原始文件和make文件,在终端上键入make depend将检查文件的依赖性,源代码如下:
EXECUTABLE=go #也可以改为别的名字
LINKCC=$(CC)
#LIBS=-lm –lpthread -lsocket 如果用于LINUX/UNIX系统,要求安装可移植线程库
LIBS=-lm –lsocket
CC=gcc #如果编译c++程序将下面gcc改为g++
CFLAGS=-Wall -g
CXX=g++
CXXFLAGS=$(CFLAGS)
SRCS:=$(wildcard *.c) $(wildcard *.cc) $(wildcard *.C)
OBJS:=$(patsubst %.c,%.o,$(wildcard *.c))
  $(patsubst %.cc,%.o,$(wildcard *.cc))
  $(patsubst %.C,%.o,$(wildcard *.C))
DEPS:=$(patsubst %.o,%.d,$(OBJS))
all:$(EXECUTABLE)
$(EXECUTABLE):$(DEPS) $(OBJS)
  $(LINKCC) $(LDFLAGS) -o $(EXECUTABLE) $(OBJS) $(LIBS)
%.d:%.c
  $(CC) -M $(CPPFLAGS) $< >$@
  $(CC) -M $(CPPFLAGS) $< |sed s/.o/.d/>$@
%.d:%.cc
  $(CXX) -M $(CPPFLAGS) $< >$@  
  $(CXX) -M $(CPPFLAGS) $< |sed s/.o/.d/>$@
%.d:%.C
  $(CXX) -M $(CPPFLAGS) $< >$@
  $(CXX) -M $(CPPFLAGS) $< |sed s/.o/.d/>$@
clean:
  -rm $(OBJS) $(EXECUTABLE) $(DEPS)
#  -rm  ./*.* 如果有一些临时的记录文件无法自动去掉加在这里
depend: $(DEPS)
  @echo "Dependencies are now up-to-date."
 
6 总结
  QNX实时操作系统的实时性很好,上下文切换时间、中断延时都非常微小,本身提供了对于多线程技术的强大支持,如果在QNX下使用多线程编程技术来解决大型并发多任务系统的控制调度,其优势是很大的,前景也是很广阔的。
 
四、05-SA8155 QNX I2C框架及代码分析
1. 描述
本文主要描述QNX I2C Drvier的相关内容,并以SA8155处理器为例讲解。
I2C 是经常用到的一种总线协议,它只占用两个IO口资源,分别是SCL时钟信号线与SDA数据线,
两根线就能将连接与总线上的设备实现数据通信,由于它的简便的构造设计,于是成为一种较为
常用的通信方式。在QNX系统里,也提供了I2C驱动框架,整体框架与SPI类似。

 

 

  I2C总线数据传输和应答

2. 目录结构
2.1 QUB配置部分
TODO 请查阅 https://blog.csdn.net/liaochaoyun/article/details/127317225
2.2. 驱动部分:
qnx_ap/AMSS/platform/hwdrivers/wired_peripherals/i2c
├── common
│ ├── aarch64
│ │ ├── a-le
│ │ ├── Makefile
│ │ └── so-le
│ ├── arm
│ │ ├── a-le-v7
│ │ ├── Makefile
│ │ └── so-le-v7
│ ├── common.mk
│ ├── Makefile
│ └── src
│ ├── I2cDeviceQup.c
│ └── I2cSys.c
├── inc
│ ├── ddii2c.h
│ ├── I2cDevice.h
│ ├── I2cError.h
│ ├── I2cTransferCfg.h
│ ├── I2cTransfer.h
│ └── I2cTypes.h
├── Makefile
└── platsvc_i2c
├── aarch64
│ ├── a-le
│ ├── Makefile
│ └── so-le
├── arm
│ ├── a-le-v7
│ ├── Makefile
│ └── so-le-v7
├── common.mk
├── inc
│ ├── I2cLog.h
│ ├── I2cPlatBam.h
│ ├── I2cPlatBsp.h
│ ├── I2cPlatSvc.h
│ └── I2cSys.h
├── Makefile
└── src
├── I2cPlatBsp.c
└── I2cPlatSvc.c
 
2.3. 资源管理器Res部分
qnx_ap/AMSS/platform/resources/i2c_drv
├── aarch64
│ ├── a-le
│ │ └── Makefile
│ ├── Makefile
│ ├── so-le
│ │ └── Makefile
│ └── so-le-g
│ └── Makefile
├── arm
│ ├── a-le-v7
│ │ └── Makefile
│ ├── Makefile
│ └── so-le-v7
│ └── Makefile
├── common.mk
├── i2c_drv.c
├── Makefile
└── protected
└── i2c_devctls.h
 
2.4 I2C Service(Demo 进程)
qnx_ap/AMSS/platform/services/daemons/i2c_service
 
├── aarch64
│ ├── Makefile
│ └── o-le
│ └── Makefile
├── arm
│ ├── Makefile
│ └── o-le-v7
│ └── Makefile
├── common.mk
├── Makefile
├── src
│ └── i2cservice_main.c
└── usefile
 
2.5 I2c API(Client)
qnx_ap/AMSS/platform/qal/clients/i2c_client
 
├── aarch64
│ ├── Makefile
│ ├── so-le
│ │ └── Makefile
│ └── so-le-g
│ └── Makefile
├── arm
│ ├── Makefile
│ └── so-le-v7
│ └── Makefile
├── common.mk
├── i2c_client.c
├── Makefile
└── public
└── amss
└── i2c_client.h
 
3. I2C API
qnx_ap/AMSS/platform/qal/clients/i2c_client/public/amss/i2c_client.h
安装:qnx_ap/install/usr/include/amss/i2c_client.h
/*
* gain access to controller/bus driver
* Returns a handle that is passed to all other functions.
* Parameters:
* (in) devname Resource Manager connection(ex. "/dev/i2c1")
*
* Returns:
* >0 success
* -1 failure
*/
int i2c_open(void* devname);
 
/*
* releases access to controller/bus driver
* Frees memory associated with "fd".
* Parameters:
* (in) fd Handle returned from init()
*
*/
void i2c_close(int fd);
 
/*
* Specify the bus clock frequency
* If an invalid bus speed is requested, this function should return
* failure and leave the bus speed unchanged.
* Parameters:
* (in) addr The slave address
* (in) fmt The slave address format
* Returns:
* 0 success
* -1 failure
*/
int i2c_set_slave_addr(int fd, uint32_t addr, uint32_t fmt);
 
/*
* Specify the bus clock frequency
* If an invalid bus speed is requested, this function should return
* failure and leave the bus speed unchanged.
* Parameters:
* (in) bus_clock_freq.
* Returns:
* 0 success
* -1 failure
*/
int i2c_set_bus_speed(int fd, uint32_t speed, uint32_t * ospeed);
 
/*
* Lock this I2C controller/bus
* Parameters:
* (in) fd Handle returned from init()
* Returns:
* 0 success
* -1 failure
*/
int i2c_bus_lock(int fd);
 
/*
* Unlock this I2C controller/bus
* Parameters:
* (in) fd Handle returned from init()
* Returns:
* 0 success
* -1 failure
*/
int i2c_bus_unlock(int fd);
 
/*
* Write
* Parameters:
* (in) fd Handle returned from init()
* (in) buf Buffer of data to write
* (in) len Length in bytes of buf
* Returns:
* Number of bytes read
*/
int i2c_write(int fd, void *buf, uint32_t len);
 
/*
* Read
* Parameters:
* (in) fd Handle returned from init()
* (in) buf Buffer for read data
* (in) len Length in bytes of buf
* Returns:
* >=0 Number of bytes read
* <0 Error # from errno.h
*/
int i2c_read(int fd, void *buf, uint32_t len);
 
/*
* Combined Write Read
* Parameters:
* (in) fd Handle returned from init()
* (in) wbuf Buffer of data to send
* (in) wlen Length in bytes of wbuf
* (in) rbuff Buffer for received data
* (in) rlen Length in bytes of wbuf
* Returns:
* >=0 Number of bytes read
* <0 Error # from errno.h
*/
int i2c_combined_writeread(int fd, void * wbuff, uint32_t wlen, void* rbuff, uint32_t rlen );
 
 
/*
* Request info about the driver.
* Returns:
* 0 success
* -1 failure
*/
int i2c_driver_info(int fd, i2c_driver_info_t *info);
 
文件引用:#include
 
4. I2C 资源管理器设计
4.1 优点
资源管理器接口的主要优点是:
它为应用程序开发人员提供了一个清晰、易于理解的思路。
它作为一个中介,在多个应用程序对一个或多个从设备之间进行访问,强制不同I2C接口之间的一致性。
对于专用的i2c总线应用程序,硬件访问库更有效;硬件接口定义了这个库的接口,有助于维护和代码可移植性。
 
4.2 通用架构
通用架构如下如:

 

 4.3 代码分析

4.3.1 服务进程
核心文件: qnx_ap/AMSS/platform/services/daemons/i2c_service/src/i2cservice_main.c
int i2c_service_base_init(void)
{
//调用i2c_drv 资源管理接口实现i2c资源管理器初始化
if (-1 == i2c_drv_init()) {
logger_log(QCLOG_AMSS_QNP_SERVICES_I2C_SERVICE, QCLOG_AMSS_I2C_SERVICE_MINOR,
QCLOG_ERROR, "i2c_drv_init Failed");
}
 
drop_abilities_i2c();
/* init sysctrl */
if(DAL_SUCCESS != sysctrl_init()) {
logger_log(QCLOG_AMSS_QNP_SERVICES_I2C_SERVICE, QCLOG_AMSS_I2C_SERVICE_MINOR,
QCLOG_ERROR, "sysctrl_init failed, errno: %s", strerror(errno));
return DAL_ERROR;
}
 
return DAL_SUCCESS;
}
 
int main(int argc, char *argv[]) {
 
int c;
...
 
/* QNX IO operation privilege */
if (ThreadCtl(_NTO_TCTL_IO, 0) == -1)
{
PROCESS_ERROR_CRITICAL("i2c_service: ThreadCtl Error");
return EXIT_FAILURE;
}
 
//信号SIGTERM处理
signal(SIGTERM,handle_sigterm);
 
// Check if process is running /dev/i2c_service
if( 0 < open(DEVICE_NAME, O_RDONLY))
{
PROCESS_ERROR_CRITICAL("Process [%s] already running.. exiting.", DEVICE_NAME);
return EXIT_FAILURE;
}
 
// Run process in background,进程后台运行
Resource_Daemonize();
 
//资源管理器实现接口
if(i2c_service_base_init() != DAL_SUCCESS)
{
PROCESS_ERROR_CRITICAL("i2c_service: i2c_service_core_init failed");
return EXIT_FAILURE;
}
//启动I2C资源管理器服务
/* the i2c resource manager main loop is going to run after here */
if(DAL_SUCCESS != sysctrl_start(DEVICE_NAME)) {
PROCESS_ERROR_CRITICAL("i2c_service: service_init failed");
return EXIT_FAILURE;
}
 
// Code should never reach here
PROCESS_ERROR_CRITICAL("Exiting i2c_service..");
return EXIT_SUCCESS;
}
 
查看系统进程:
# pidin | grep i2c
40982 1 bin/i2c_service 10r RECEIVE 8
40982 2 bin/i2c_service 15r RECEIVE 2
40982 3 bin/i2c_service 21r SEM fff808a056320f94
40982 4 bin/i2c_service 41r INTR
40982 5 bin/i2c_service 10r RECEIVE 3
40982 6 bin/i2c_service 41r INTR
40982 7 bin/i2c_service 10r RECEIVE 6
40982 8 bin/i2c_service 41r INTR
40982 9 bin/i2c_service 10r RECEIVE 11
 
4.3.2 资源管理器实现
核心文件:qnx_ap/AMSS/platform/resources/i2c_drv/i2c_drv.c
入口:
/******************************************************************************/
/***************************** startup and config ****************************/
/******************************************************************************/
int i2c_drv_init(void)
{
int i;
int idx = 0;
int ret = EOK;
uint64_t chip_id = 0;
const void *fdt_paddr = 0;
pthread_t threadID;
DALSYSPropertyVar PropVar;
 
DALSYS_PROPERTY_HANDLE_DECLARE(hDALProps);
DALSYS_InitMod(NULL);
DALSYS_RegisterMod(&gDALModDriverInfoList);
 
int policy;
struct sched_param param;
pthread_attr_t attr;
 
if (waitfor_attach(QCORE_SERVICE, 5000))
{
I2C_SLOGE("Timed out waiting for %s to be ready", QCORE_SERVICE);
return -1;
}
 
fdt_paddr = fdt_get_root();
if (!fdt_paddr) {
I2C_SLOGE("Failed to load device tree");
return -1;
}
ret = fdt_foreach_subnode_byname((void*) fdt_paddr , "/chip_info",
&get_chip_info, &chip_id);
if (ret) {
I2C_SLOGE("Failed to find dt chip_info");
return -1;
}
 
I2C_SLOGI("Found chip id=%d", (int)chip_id);
 
//线程配置
pthread_attr_init(&attr);
pthread_getschedparam(pthread_self(), &policy, ¶m);
param.sched_priority = 100;
pthread_attr_setschedparam(&attr, ¶m);
 
devs = calloc(MAX_NUM_I2C_DEVS, sizeof(i2c_dev_t));
if(devs == NULL)
return -1;
 
//创建设备
// create devices , if specified via command line
for(i = 0; i < MAX_NUM_I2C_DEVS; i++)
{
if(DALSYS_GetDALPropertyHandle(DeviceID[i], hDALProps) == DAL_SUCCESS)
{
//解析I2C配置,详情看QUP-I2C配置项
if (DAL_SUCCESS != DALSYS_GetPropertyValue(hDALProps, "I2C_ENABLED", 0, &PropVar)
|| PropVar.Val.dwVal == 0)
{
continue;
}
 
if (DAL_SUCCESS != DALSYS_GetPropertyValue(hDALProps, "CLOCK_SE_NAME", 0, &PropVar)
|| PropVar.Val.pszVal == 0)
{
I2C_SLOGE("Failed to get property CLOCK_SE_NAME");
return -1;
}
 
if (!strncmp(PropVar.Val.pszVal, "scc", 3)) {
devs[idx].is_ssc = true;
}
 
//设备编号,名称等
snprintf(devs[idx].devname, MAX_NUM_DEVNAME, "/dev/i2c%d", i+1);
devs[idx].DALDeviceID = DeviceID[i];
devs[idx].index = idx;
devs[idx].bus_active = 0;
devs[idx].timer_created = 0;
 
//QUP I2C设备初始化
if (DAL_SUCCESS != I2CDEV_Init(DeviceID[i], &devs[idx].ahI2cDev)) {
I2C_SLOGE("Failed to initialize I2C %s", devs[idx].devname);
continue;
}
devs[idx].initialized = 1;
 
//通过线程创建设备,资源管理器及接口。
ret = pthread_create(&threadID, NULL, (void *)&device_main,
(void *)&devs[idx]);
if (EOK == ret) {
pthread_setname_np(threadID, devs[idx].devname);
idx++;
} else {
I2C_SLOGE("Couldn't create RM thread for device-%d:"
"name-%s:ret-%d",
DeviceID[i], devs[idx].devname, ret);
}
}
}
 
if (ID_6155 == chip_id) {
if ((ret = i2c_register_ssr())) {
I2C_SLOGE("Failed to register for SSR ret=%x", ret);
return -1;
}
}
 
return 0;
}
 
资源管理器创建通用步骤:
1 建立一个上下文切换句柄dpp = dispatch_create();这个东东主要用在mainloop中产生一个block特性,可以让我们等待接受消息;
2 iofunc初始化。这一步是将自己实现的函数与POSIX层函数进行接口,解析从read、write、devctl等函数传来的消息进行解析,以实现底层与应用层函数之间的交互,通过io_funcs.read = io_read,io_funcs.write = io_write,进行函数重载;
3 注册设备名,使设备在命名空间中产生相应的名称,这一点是整个过程的关键了,形如 pathID = resmgr_attach (dpp, &rattr, "/dev/Null",_FTYPE_ANY, 0, &connect_funcs,&io_funcs, &ioattr),这样不仅注册了一个设备名,还让系统知道了我们实习的IO函数对应关系;
4 为之前创建的上下文句柄分配空间,例如ctp = dispatch_context_alloc (dpp);为了第六步使用;
5 通过不断循环等待dispatch_block()来调用MsgReceive()使Resource manger处于receive block状态,以接收上层发送来的消息,通过dispatch_handler (ctp)去调用我们自己定义的IO函数
 
资源管理器创建实现:
int device_main(i2c_dev_t *dev)
{
resmgr_connect_funcs_t connect_funcs;
resmgr_io_funcs_t io_funcs;
resmgr_attr_t rattr;
iofunc_funcs_t ocb_funcs = { _IOFUNC_NFUNCS, _ocb_calloc, _ocb_free,
NULL, NULL, NULL };
iofunc_mount_t mount = { 0, IOFUNC_PC_ACL, 0, 0, &ocb_funcs };
I2CDEV_PowerStates power_state = 0;
pthread_condattr_t cond_attr = { 0 };
 
/* Set up contiguous buffers */
dev->fd_mmap = posix_typed_mem_open("/ram/dma",
O_RDWR,
POSIX_TYPED_MEM_ALLOCATE_CONTIG);
if (dev->fd_mmap == -1) {
I2C_SLOGE("%s: posix_typed_mem_open(/ram/dma) failed(%d-%s)",
__FUNCTION__, errno, strerror(errno));
exit(1);
}
 
/* Configure clocks, gpios at init-time*/
I2CDEV_GetPowerState(dev->ahI2cDev, &power_state);
if (POWER_STATE_2 != power_state) {
I2CDEV_SetPowerState(dev->ahI2cDev , POWER_STATE_2);
dev->bus_active = 1;
}
 
pthread_mutex_init(&dev->mutex, NULL);
 
pthread_condattr_init(&cond_attr);
pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC);
pthread_cond_init(&dev->cond, &cond_attr);
 
//创建channel
dev->chid = ChannelCreate(_NTO_CHF_DISCONNECT | _NTO_CHF_UNBLOCK);
if(dev->chid == -1) {
I2C_SLOGE("ChannelCreate failed (%s)", strerror(errno));
exit (1);
}
 
//第一步
/*
* allocate and initialize a dispatch structure for use by our
* main loop
*/
I2C_SLOGI("initializing %s\n",dev->devname);
dev->dpp = dispatch_create_channel ( dev->chid, 0);
if (dev->dpp == NULL)
{
I2C_SLOGE("couldn't dispatch_create: %s\n",strerror(errno));
exit (1);
}
 
 
/*
* set up the resource manager attributes structure, we'll
* use this as a way of passing information to resmgr_attach().
* For now, we just use defaults.
*/
 
memset (&rattr, 0, sizeof (rattr)); /* using the defaults for rattr */
rattr.nparts_max = 2; /* Max iov used by client is 2 */
rattr.msg_max_size = MAX_NUM_I2C_WRITE;
 
//第二步 iofunc初始化
/*
* intialize the connect functions and I/O functions tables to
* their defaults by calling iofunc_func_init().
*
* connect_funcs, and io_funcs variables are already declared.
*
*/
iofunc_func_init (_RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs);
 
/* over-ride the connect_funcs handler for open with our io_open,
* and over-ride the io_funcs handlers for read and write with our
* io_read and io_write handlers
*/
connect_funcs.open = io_open;
io_funcs.devctl = io_devctl;
io_funcs.acl = io_acl;
io_funcs.close_ocb = io_close;
 
/* initialize our device description structure
*/
 
iofunc_attr_init (&dev->hdr, S_IFCHR | 0666, NULL, NULL);
 
dev->hdr.mount = &mount; // so we can alloc an OCB per open
 
iofunc_acl_init(&dev->hdr, iofunc_acl_posix_ctrl, 0);
 
//第三步 注册设备
/*
* call resmgr_attach to register our prefix with the
* process manager, and also to let it know about our connect
* and I/O functions.
*
* On error, returns -1 and errno is set.
*/
dev->pathID = resmgr_attach(dev->dpp,
&rattr,
dev->devname,
_FTYPE_ANY,
0,
&connect_funcs,
&io_funcs,
(RESMGR_HANDLE_T *)dev);
if (dev->pathID == -1)
{
I2C_SLOGE(" couldn't attach pathname: %s\n", strerror (errno));
exit (1);
}
 
//第四步 为dpp分配句柄空间
dispatch_context_t *ctp;
dev->ctp = dispatch_context_alloc(dev->dpp);
if (dev->ctp == NULL)
{
dispatch_destroy(dev->dpp);
return EXIT_FAILURE;
}
 
/* Notify bmetrics I2C device is ready */
int fd = open("/dev/bmetrics", O_WRONLY);
if (fd == -1) {
I2C_SLOGE("Failed to open /dev/bmetrics with error:%d\n", errno);
} else {
char buf[BM_KPI_BUF_SIZE];
snprintf(buf, BM_KPI_BUF_SIZE, "bootmarker DRIVER I2C Ready:%s\n",
dev->devname);
if (-1 == write(fd, buf, strlen(buf))) {
I2C_SLOGE("Failed to write /dev/bmetrics with error:%d\n", errno);
}
close(fd);
}
 
/* register LPM pulses */
if (EOK != i2c_register_pulse(dev)) {
I2C_SLOGE("pulse registration for %s failed", dev->devname);
exit(1);
}
 
/* register SSR pulses */
dev->ssr_coid = ConnectAttach(ND_LOCAL_NODE, 0 /* pid */,
dev->chid, _NTO_SIDE_CHANNEL, 0);
if (-1 == dev->ssr_coid) {
I2C_SLOGE("I2C_RM SSR ConnectAttach failed (%s)", strerror(errno));
exit(1);
}
 
I2C_SLOGE("Initialized %s", dev->devname);
 
//第五步 通过不断循环等待dispatch_block()来调用MsgReceive()使Resource manger处于receive block状态,以接收上层发送来的消息,通过dispatch_handler (ctp)去调用我们自己定义的IO函数
while(1) {
if ((ctp = dispatch_block(dev->ctp)) == NULL) {
I2C_SLOGE("dispatch_block failed");
exit(1);
}
dispatch_handler(dev->ctp);
}
 
return 0;
}
 
4.4 API与资源管理器之间的关联
4.4.1 标准框架如下:

 4.4.2 具体实现

TODO
拿devctl为例:

 5. 梳理

6. 案例
TODO
//伪代码如下
#include
 
#define DEV_NAME "/dev/i2c_1"
 
int fd = -1;
int slaveAddr = 0xAA;
 
main()
{
unsigned char writedata[3];
writedata[0] = 0x11;
writedata[1] = 0x22;
writedata[2] = 0x22;
 
fd = i2c_open(DEV_NAME );
i2c_bus_lock ( fd );
i2c_set_slave_addr(fd, slaveAddr, 0))
i2c_write(fd, writedata, sizeof(writedata));
}
 
五、QNX编译选项
编译时警告选项
检测到易受攻击的代码时输出警告。有问题的代码容易崩溃、产生错误或意外行为,或为攻击创造机会,在开发阶段如果能充分识别并处理这些信息,能显著提高我们的代码健壮性,典型的编译如表1所示。
 表1 QNX警告编译选项
编译选项
描述
-Wall
启用有关可疑代码构造的附加警告。包括 -Wformat,它检查对 printf() 和 scanf() 样式函数的调用,以确保参数与格式字符串匹配。
-Wcast-align
如果指针cast导致目标所需的对齐方式增加,则发出警告。
-Wcast-qual
每当转换指针的方式从目标type中删除type限定符时发出警告。
-Wconversion
警告可能改变值的隐式转换。
-Wduplicated-branches
当 if-else 具有相同的分支时发出警告。
-Wduplicated-cond
警告 if-else-if 链中的重复条件。
-Werror
将警告视为错误,使构建失败。可能不适用于所有环境。
-Wextra
启用 -Wall 未启用的其他警告。
-Wfloat-equal
如果在相等比较中使用浮点值,则发出警告。
-Wformat=2
检查对 printf() 和 scanf() 样式函数的调用,以确保参数与格式字符串 (-Wformat) 匹配,以及额外的格式检查 (-Wformat-nonliteral、-Wformat-security、-Wformat-y2k)。
-Winit-self
警告自己初始化的未初始化变量(需要 -Wuninitialized,包含在 -Wall 中)。
-Wlogical-op
警告表达式中逻辑运算符的可疑使用。
-Wmissing-declarations
如果在没有先前声明的情况下定义了全局函数,则发出警告。
-Wmissing-prototypes
如果缺少原型,请发出警告。
-Wnull-dereference
如果编译器检测到由于取消引用空指针而触发错误或未定义行为的路径,则发出警告。
-Wpointer-arith
警告任何取决于“size of”函数type或 void。
-Wshadow
每当局部变量或type声明遮蔽另一个变量、参数、type或类成员(在 C++ 中),或者当一个内置函数被遮蔽时发出警告。
-Wsuggest-attribute=format
警告可能成为格式属性候选的函数指针。设置格式属性后,-Wformat=2 标志会查找函数调用的问题。
-Wswitch-default
每当 switch 语句没有默认情况时发出警告。
-Wswitch-enum
只要 switch 语句具有枚举type的索引并且缺少该枚举的一个或多个命名代码的case,就会发出警告。
-Wtrampolines
当为指向嵌套函数的指针生成“trampolines”时发出警告。(trampoline是运行时取嵌套函数地址时在栈上创建的一小段数据或代码,用于间接调用嵌套函数。)
-Wunreachable-code
如果编译器检测到永远不会执行的代码,则发出警告。
-Wwrite-strings
如果 const char * 转换为(非 const)char *,则发出诊断消息。
  • 地址无关代码
对可执行文件、库文件编译时开启下面的编译选型,并启用ASLR。启用该选项后系统对可执行文件、库文件装载时地址空间布局会随机化。一般而言,QNX会默认开启-fPIC、-fPIE和ASLR。
表2 地址无关代码编译选项
编译选项
描述
-fPIC
将库编译为与位置无关的代码 (PIC)
-fPIE
将可执行文件编译为与位置无关(与位置无关的可执行文件 (PIE))。
在这里简单介绍下ASLR。ASLR,即地址空间布局随机化(Address Space Layout Randomization),如图3所示,每次加载可执行文件时,地址空间布局随机化会改变数据和指令的位置。要支持 ASLR,二进制文件必须编译为与位置无关,因此需要使用 -fPIC编译库,使用-fPIE编译二进制文件。

 图 地址随机化效果

地址空间布局随机化针对以下系统元素实施:
Ø可执行代码——由动态加载器加载的标记为可执行的程序段。应用程序必须使用 PIE 编译。
Ø堆栈——页面和页面内的偏移量都是随机的。
Ø堆——页面是随机的。
Ø共享内存——多个进程之间共享的内存映射到每个进程中不同的随机虚拟地址。
Ølibc 和动态加载器——libc 的位置和动态加载器的位置在进程的虚拟地址空间中都是随机的。虽然 libc 和动态加载器等共享对象映射到随机地址,但如果加载顺序保持不变,则它们相对于彼此映射到相同地址。
Ø命令行参数——命令行参数在进程的虚拟地址空间中的位置是随机的。
  • 堆栈保护
编译时针对应用程序、共享库使用,在函数返回之前检查堆栈canary以确保它们没有被修改(例如,由于堆栈分配的变量上的缓冲区溢出)。对堆栈上的堆栈canary的任何修改都将导致进程中止。可用的编译选项如表3所示。
表3 堆栈保护编译选项
编译选项
描述
-fstack-protector
保护在其堆栈上声明长度为 8 个或更多字节的字符数组的任何函数
-fstack-protector-all
保护所有函数
-fstack-protector-strong
默认,在 -fstack-protector 和 -fstack-protector-all 之间进行平衡,此option的目的是通过扩大堆栈保护范围而不将其扩展到程序中的每个函数来获得性能,同时牺牲很少的安全性。
  • 阻止未定义符号
编译时防止目标文件中有未定义符号,通过指定-Wl,-z,defs选项,这也是减少攻击面的措施之一。当然也可以不指定,未定义符号在进行代码审计后也基本能识别出。
表4 阻止未定义符号编译选项
编译选项
描述
-Wl,-z,defs
防止目标文件中的未定义符号
  • GOT表保护
编译时对可执行指定表5中编译选项,可利用RELRO技术,在动态加载程序完成加载和链接可执行文件后,编译器可以将可执行文件的重定位部分标记为只读。 如果出现 .bss 或数据溢出错误,RELRO(只读重定位)保护 ELF 二进制文件中的全局偏移表 (GOT) 不被覆盖。
表5 GOT表保护编译选项
编译选项
描述
-Wl,-z,relro
使能partial RELRO,不会将整个GOT作为read-only
-Wl,-z,now
使能full RELRO,使进程链接表 (PLT) 以及 .got 和 .dtors 文件成为只读文件。相对于partial RELRO,会影响性能
检查
qchecksec命令
  • 检查未定义行为
编译时启用sanitizer编译代码,从而增加在代码中发现错误的可能性。目前,QNX Neutrino 支持 Undefined Behavior Sanitizer (UBSAN)。
表6 检查未定义行为选项
编译option
描述
--fsanitize=undefined
启用 UBSAN。各种计算被用来检测运行时未定义的行为。
在此强调一下,
3.查看编译时保护选项
上面介绍了那么多编译选项,特别是对于堆栈保护、GOT表保护、地址无关代码,我们如何识别运行的应用程序或库文件已经开启了对应的编译呢?笔者在此推荐两种方法:
  • 通过readelf读取
readelf是一个可用于检查ELF(Executable and Linkable Format)文件的工具,该文件格式是Linux和QNX等操作系统上可执行文件和共享库的标准格式之一。
检查项
步骤
期望结果
fPIE/fPIC
readelf -d | grep 'FL_PIC|FL_PIE'
是要检查的二进制文件
如果该文件已启用fpic或fpie,则输出应包含以下内容:
0x0000000E (FLAGS)              PN_FLAGS 0x7:
FL_PIE|FL_EXEC_PSEUDO_RELOCS|FL_EXEC_STACK
堆栈保护
readelf -s | grep '__stack_chk_fail'
输出包含__stack_chk_fail符号,则表示堆栈保护已启用
GOT表保护
readelf -r | grep '__guard'
输出包含__guard符号,则表示GOT表保护已启用
  • 通过qchecksec读取
类似于Linux中的checksec,qchecksec是一个用于检查二进制文件的安全性选项的工具。可以从github上的qchecksec存储库下载qchecksec源代码,然后使用GNU Autotools进行编译和安装。一旦安装完成,可以使用qchecksec命令来检查二进制文件的安全性选项。例如,要检查名为test的二进制文件的选项,可以运行以下命令:
qchecksec test
这将输出有关二进制文件的各种安全选项的信息。例如,以下是一些可能的输出:
RELRO STACK  CANARY  NX  PIE  RPATH    RUNPATH  FORTIFY  Fortified
Full No   NX enabled    PIE enabled    No         No        Yes     2
输出中的每一列都对应于一种安全选项,其中:
·RELRO:表示RELRO保护的级别。
·STACK CANARY:表示堆栈保护是否已启用。
·NX:表示内存不可执行是否已启用。
·PIE:表示位置无关执行是否已启用。
·RPATH:表示是否设置了RPATH。
·RUNPATH:表示是否设置了RUNPATH。
·FORTIFY:表示是否启用了FORTIFY Source。
输出中的最后一列"FORTIFIED"表示是否已应用FORTIFY_SOURCE保护。如果数字大于0,则该文件已使用FORTIFY_SOURCE进行了保护,并且数字表示保护级别。
小结
这些编译器选项并不意味着在量产二进制文件中启用。 每个选项都针对代码的不同方面。 要充分利用这些选项的功能,请确保检测代码像在量产环境一样被执行。
 
六、QNX Neutrino 进程间通信编程之Shared Memory
介绍
Interprocess Communication(IPC,进程间通信)在QNX Neutrino从一个嵌入式实时系统向一个全面的POSIX系统转变起着至关重要的作用。IPC是将在内核中提供各种服务的进程内聚在一起的粘合剂。在QNX中,消息传递是IPC的主要形式,也提供了其他的形式,除非有特殊的说明,否则这些形式也都是基于本地消息传递而实现的。
将更高级别的 IPC 服务(如通过我们的消息传递实现的管道和 FIFO)与其宏内核对应物进行比较的基准测试表明性能相当。
QNX Neutrino提供以下形式的IPC:
Service: Implemented in:
Message-passing Kernel
Pules Kernel
Signals Kernel
Event Delivery External process
POSIX message queues External process
Shared memory Process manager
Pipes External process
FIFOs External process
本篇幅介绍的是POSIX IPC Shared Memory。
共享内存提供了最高带宽的IPC机制,一旦创建了共享内存对象,访问对象的进程可以使用指针直接对其进行读写操作。共享内存本身是不同步的,需要结合同步原语一起使用,信号量和互斥锁都适合与共享内存一块使用,信号量一般用于进程之间的同步,而互斥锁通常用于线程之间的同步,通通常来说互斥锁的效率会比信号量要高。
共享内存与消息传递结合起来的IPC机制,可以提供以下特点:
  • 非常高的性能(共享内存)
  • 同步(消息传递)
  • 跨网络传递(消息传递)
QNX中消息传递通过拷贝完成,当消息较大时,可以通过共享内存来完成,发送消息时不需要发送整个消息内容,只需将消息保存到共享内存中,并将地址传递过去即可。通常会使用mmap来将共享内存区域映射到进程地址空间中来,如下图所示:

进程中的线程之间自动共享内存。通过设置shared memory,同样的物理内存可以被多个进程访问。

共享内存建立流程:
fd = shm_open( “/myname”, O_RDWR|O_CREAT, 0666 );
// 共享内存的名字必须是唯一的。
ftruncate( fd, SHARED_SIZE );
// 通过ftruncate分配共享内存对象大小,这个SHARED_SIZE将四舍五入为页面(4KB)大小的倍数。
ptr = mmap( NULL, SHARED_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );
//mmap 将对应的物理地址空间映射出相应的虚拟地址空间,然后下一步并将其初始化。
close(fd);
//你不在使用文件句柄的时候,你需要将它关闭
 
共享内存访问流程:
fd = shm_open( “/myname”,O_RDWR );
//使用共享内存是,共享内存名必须一样。
ptr = mmap( NULL, SHARED_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );
//如果对共享内存只读访问,那么不必使用PROT_WRITE属性。
//如果对共享内存模块子块进行访问,只需要将最后一个参数offset零替换成你对应子块的偏移,大小也需要做相应的调整。
close(fd);
//你不在使用文件句柄的时候,你需要将它关闭
 
清除共享内存流程:
// 每个句柄,mapping,共享内存名都是一个参考依据
// 可以明确关闭和取消映射
close(fd);
munmap( ptr, SHARED_SIZE );
// 在进程死亡时,所有 fds 都会自动关闭并且所有映射都未映射
// 必须明确删除名称:
shm_unlink( “/myname” );
// 在开发和测试期间,这可以从命令行手动完成:
rm /dev/shmem/myname
 
共享内存的问题:
– readers不知道数据何时稳定 – writers不知道什么时候写作是安全的
 
所以这样涉及到了同步的操作。
共享内存区域中的线程同步对象
如果使用 sem_init(),则 pshared 参数必须非零
互斥体和条件变量需要 PTHREAD_PROCESS_SHARED 标志属性
使用atomic_*() 函数来操作变量
IPC
MsgSend()/MsgReceive()/MsgReply() 具有内置同步功能
使用共享内存避免大数据拷贝
 
Process进程间通过shared memory 通信同步策略:

POSIX共享内存API
Function Description Classification
shm_open() Open (or create) a shared memory region. POSIX
close() Close a shared memory region. POSIX
mmap() Map a shared memory region into a process's address space. POSIX
munmap() Unmap a shared memory region from a process's address space. POSIX
munmap_flags() Unmap previously mapped addresses, exercising more control than possible with munmap() QNX Neutrino
mprotect() Change protections on a shared memory region. POSIX
msync() Synchronize memory with physical storage. POSIX
shm_ctl(), shm_ctl_special() Give special attributes to a shared memory object. QNX Neutrino
shm_unlink() Remove a shared memory region. POSIX
 
函数shm_open和shm_unlink非常类似于为普通文件所提供的open和unlink系统调用。如果要编写一个可移植的程序,那么shm_open和shm_unlink是最好的选择。
 
shm_open:创建一个新的共享区域或者附加在已有的共享区域上.区域被其名字标识,函数返回各文件的描述符。
 
#include
#include /* For mode constants */
#include /* For O_* constants */
int shm_open(const char *name, int oflag, mode_t mode);
 
参数:
name: 共享内存名字;
oflag: 与open函数类型, 可以是O_RDONLY, O_WRONLY, O_RDWR, 还可以按位或上O_CREAT, O_EXCL, O_TRUNC.
mode: 此参数总是需要设置, 如果oflag没有指定O_CREAT, 则mode可以设置为0;
返回值:
成功: 返回一个文件描述符;
失败: 返回-1;
 
ftruncate:修改共享内存大小。
int ftruncate(int fd, off_t length);
参数:
fd:文件描述符
length:长度
返回值:
成功返回0,失败返回-1
 
shm_unlink:类似于unlink系统调用对文件进行操作,直到所有的进程不再引用该内存区后才对其进行释放。
int shm_unlink(const char *name);
参数:
name:共享内存对象的名字
返回值:
成功返回0,失败返回-1
 
mmap:用于将一个文件映射到某一内存区中,其中也使用了shm_open函数返回的文件描述符。
munmap:用于释放mmap所映射的内存区域。
 
#include
void * mmap( void *where_i_want_it,
size_t length,
int memory_protections,
int mapping_flags,
int fd,
off_t offset_within_shared_memory );
int munmap(void *addr, size_t length);
 
参数:
 
where_i_want_it: 要映射的起始地址, 通常指定为NULL, 让内核自动选择;
 
length: 映射到进程地址空间的字节数, 通常是先前已经创建的共享内存的大小;
 
memory_protections: 映射区保护方式(见下);
 
mmap()的返回值将是进程映射对象的地址空间中的地址。参数 where_i_want_it 用作系统提示您放置对象的位置。如果可能,该对象将被放置在所请求的地址。大多数应用程序指定的地址为零,这使系统可以自由地将对象放置在所需的位置。
 
可以为 memory_protections 指定以下保护类型:
PROT_EXEC表示映射的内存页可执行
PROT_READ表示映射的内存可被读
PROT_WRITE表示映射的内存可被写
PROT_NONE表示映射的内存不可访问
 
当您使用共享内存区域访问可由硬件修改的双端口内存(例如,视频帧缓冲区或内存映射网络或通信板)时,应使用 PROT_NOCACHE 清单。如果没有此清单,处理器可能会从先前缓存的读取中返回“陈旧”数据。
 
mapping_flags: 标志(通常指定为MAP_SHARED, 用于进程间通信);
 
这些标志分为两部分-第一部分是类型,必须指定为以下之一:
 
MAP_SHARED表示共享这块映射的内存,读写这块内存相当于直接读写文件,这些操作对其他进程可见,由于OS对文件的读写都有缓存机制,所以实际上不会立即将更改写入文件,除非带哦用msync()或mumap()
MAP_PRIVATE表示创建一个私有的copy-on-write的映射, 更新映射区对其他映射到这个文件的进程是不可见的
 
fd: 文件描述符(填为shm_open返回的共享内存ID);
 
offset_within_shared_memory: 从文件头开始的偏移量(一般填为0);
 
mmap返回值:
 
成功: 返回映射到的内存区的起始地址;
 
失败: 返回-1;
 
msync:同步存取一个映射区域并将高速缓存的数据回写到物理内存中,以便其他进程可以监听这些改变。
 
#include
int msync(void *start, size_t length, int flags);
 
flags值可为 MS_ASYNC,MS_SYNC,MS_INVALIDATE
 
MS_ASYNC的作用是,不管映射区是否更新,直接冲洗返回。
MS_SYNC的作用是,如果映射区更新了,则冲洗返回,如果映射区没有更新,则等待,知道更新完毕,就冲洗返回。
MS_INVALIDATE的作用是,丢弃映射区中和原文件相同的部分。
 
具体实例如下:

 客户端进程代码如下:

  /*
*
* client.c: Write strings for printing in POSIX shared memory object
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
 
// Buffer data structures
#define MAX_BUFFERS 10
 
#define LOGFILE "/tmp/example.log"
 
#define SEM_MUTEX_NAME "/sem-mutex"
#define SEM_BUFFER_COUNT_NAME "/sem-buffer-count"
#define SEM_SPOOL_SIGNAL_NAME "/sem-spool-signal"
#define SHARED_MEM_NAME "/posix-shared-mem-example"
 
struct shared_memory {
char buf [MAX_BUFFERS] [256];
int buffer_index;
int buffer_print_index;
};
 
void error (char *msg);
 
int main (int argc, char **argv)
{
struct shared_memory *shared_mem_ptr;
sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem;
int fd_shm;
char mybuf [256];
 
// mutual exclusion semaphore, mutex_sem
if ((mutex_sem = sem_open (SEM_MUTEX_NAME, 0, 0, 0)) == SEM_FAILED)
error ("sem_open");
 
// Get shared memory
if ((fd_shm = shm_open (SHARED_MEM_NAME, O_RDWR, 0)) == -1)
error ("shm_open");
 
if ((shared_mem_ptr = mmap (NULL, sizeof (struct shared_memory), PROT_READ | PROT_WRITE, MAP_SHARED,
fd_shm, 0)) == MAP_FAILED)
error ("mmap");
 
// counting semaphore, indicating the number of available buffers.
if ((buffer_count_sem = sem_open (SEM_BUFFER_COUNT_NAME, 0, 0, 0)) == SEM_FAILED)
error ("sem_open");
 
// counting semaphore, indicating the number of strings to be printed. Initial value = 0
if ((spool_signal_sem = sem_open (SEM_SPOOL_SIGNAL_NAME, 0, 0, 0)) == SEM_FAILED)
error ("sem_open");
 
char buf [200], *cp;
 
printf ("Please type a message: ");
 
while (fgets (buf, 198, stdin)) {
// remove newline from string
int length = strlen (buf);
if (buf [length - 1] == '\n')
buf [length - 1] = '\0';
 
// get a buffer: P (buffer_count_sem);
if (sem_wait (buffer_count_sem) == -1)
error ("sem_wait: buffer_count_sem");
 
/* There might be multiple producers. We must ensure that
only one producer uses buffer_index at a time. */
// P (mutex_sem);
if (sem_wait (mutex_sem) == -1)
error ("sem_wait: mutex_sem");
 
// Critical section
time_t now = time (NULL);
cp = ctime (&now);
int len = strlen (cp);
if (*(cp + len -1) == '\n')
*(cp + len -1) = '\0';
sprintf (shared_mem_ptr -> buf [shared_mem_ptr -> buffer_index], "%d: %s %s\n", getpid (),
cp, buf);
(shared_mem_ptr -> buffer_index)++;
if (shared_mem_ptr -> buffer_index == MAX_BUFFERS)
shared_mem_ptr -> buffer_index = 0;
 
// Release mutex sem: V (mutex_sem)
if (sem_post (mutex_sem) == -1)
error ("sem_post: mutex_sem");
 
// Tell spooler that there is a string to print: V (spool_signal_sem);
if (sem_post (spool_signal_sem) == -1)
error ("sem_post: (spool_signal_sem");
 
printf ("Please type a message: ");
}
 
if (munmap (shared_mem_ptr, sizeof (struct shared_memory)) == -1)
error ("munmap");
exit (0);
}
 
// Print system error and exit
void error (char *msg)
{
perror (msg);
exit (1);
}
 
服务器端进程代码如下:
 
/*
*
* logger.c: Write strings in POSIX shared memory to file
* (Server process)
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
 
// Buffer data structures
#define MAX_BUFFERS 10
 
#define LOGFILE "/tmp/example.log"
 
#define SEM_MUTEX_NAME "/sem-mutex"
#define SEM_BUFFER_COUNT_NAME "/sem-buffer-count"
#define SEM_SPOOL_SIGNAL_NAME "/sem-spool-signal"
#define SHARED_MEM_NAME "/posix-shared-mem-example"
 
struct shared_memory {
char buf [MAX_BUFFERS] [256];
int buffer_index;
int buffer_print_index;
};
 
void error (char *msg);
 
int main (int argc, char **argv)
{
struct shared_memory *shared_mem_ptr;
sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem;
int fd_shm, fd_log;
char mybuf [256];
 
// Open log file
if ((fd_log = open (LOGFILE, O_CREAT | O_WRONLY | O_APPEND | O_SYNC, 0666)) == -1)
error ("fopen");
 
// mutual exclusion semaphore, mutex_sem with an initial value 0.
if ((mutex_sem = sem_open (SEM_MUTEX_NAME, O_CREAT, 0660, 0)) == SEM_FAILED)
error ("sem_open");
 
// Get shared memory
if ((fd_shm = shm_open (SHARED_MEM_NAME, O_RDWR | O_CREAT, 0660)) == -1)
error ("shm_open");
 
if (ftruncate (fd_shm, sizeof (struct shared_memory)) == -1)
error ("ftruncate");
 
if ((shared_mem_ptr = mmap (NULL, sizeof (struct shared_memory), PROT_READ | PROT_WRITE, MAP_SHARED,
fd_shm, 0)) == MAP_FAILED)
error ("mmap");
// Initialize the shared memory
shared_mem_ptr -> buffer_index = shared_mem_ptr -> buffer_print_index = 0;
 
// counting semaphore, indicating the number of available buffers. Initial value = MAX_BUFFERS
if ((buffer_count_sem = sem_open (SEM_BUFFER_COUNT_NAME, O_CREAT, 0660, MAX_BUFFERS)) == SEM_FAILED)
error ("sem_open");
 
// counting semaphore, indicating the number of strings to be printed. Initial value = 0
if ((spool_signal_sem = sem_open (SEM_SPOOL_SIGNAL_NAME, O_CREAT, 0660, 0)) == SEM_FAILED)
error ("sem_open");
 
// Initialization complete; now we can set mutex semaphore as 1 to
// indicate shared memory segment is available
if (sem_post (mutex_sem) == -1)
error ("sem_post: mutex_sem");
 
while (1) { // forever
// Is there a string to print? P (spool_signal_sem);
if (sem_wait (spool_signal_sem) == -1)
error ("sem_wait: spool_signal_sem");
 
strcpy (mybuf, shared_mem_ptr -> buf [shared_mem_ptr -> buffer_print_index]);
 
/* Since there is only one process (the logger) using the
buffer_print_index, mutex semaphore is not necessary */
(shared_mem_ptr -> buffer_print_index)++;
if (shared_mem_ptr -> buffer_print_index == MAX_BUFFERS)
shared_mem_ptr -> buffer_print_index = 0;
 
/* Contents of one buffer has been printed.
One more buffer is available for use by producers.
Release buffer: V (buffer_count_sem); */
if (sem_post (buffer_count_sem) == -1)
error ("sem_post: buffer_count_sem");
 
// write the string to file
if (write (fd_log, mybuf, strlen (mybuf)) != strlen (mybuf))
error ("write: logfile");
}
}
 
// Print system error and exit
void error (char *msg)
{
perror (msg);
exit (1);
}
 
客户端和服务器端编译命令和输出结果如下:
 
gcc -pthread client.c -lrt -o client
gcc -pthread server.c -lrt -o server
# 服务器进程加载
bspserver@ubuntu:~/workspace/posix_share_memory$ ./server
 
# 客户进程 A
bspserver@ubuntu:~/workspace/posix_share_memory$ ./client
Please type a message: Fuck U~ Client A
 
# 客户进程 B
bspserver@ubuntu:~/workspace/posix_share_memory$ ./client
Please type a message: I Love U! Client B
 
# 客户进程 C
bspserver@ubuntu:~/workspace/posix_share_memory$ ./client
Please type a message: Hello World! Client C
 
#在/tmp/目录下查询example.log日志
bspserver@ubuntu:~/workspace/posix_share_memory$cat /tmp/example.log
2785: Fri Dec 17 02:02:11 2021 Hello World! Client C
2695: Fri Dec 17 02:02:40 2021 I Love U! Client B
2788: Fri Dec 17 02:04:40 2021 Fuck U~ Client A
 
参考文献:
 
Programming with POSIX Threads
System Architecture - Interprocess Communication (IPC)
 
posted @ 2025-07-10 08:52  91program  阅读(536)  评论(0)    收藏  举报