一 QNX概述
QNX实时操作系统,采用微内核和基于消息的进程间通信。
QNX的系统进程包括:
ProcessManager,FilesystemManager,DeviceManager,NetworkManager
1 进程间通信IPC:
1)消息Message,QNX最基本的IPC,提供进程间同步通信,发送者和接受者有应答
服务器????
ChannelId = ChannelCreate(Flags);
客户端???
ConnectionId = ConnectAttach(Node, Pid, Chid, Index, Flag);
Node标识机器号,如果客户端与服务器在同一台机器里时,=0,或=ND_LOCAL_NODE;
pid标识该服务的进程号;
chid就是服务器调用 ChannelCreate()后得到的channelId频道号了。
基本上客户端就是同"Node这台机器里的,Pid这个进程的,Chid频道"做一个连接。有了连接以后,就可以进行消息传递了。因为一个进程可能有多个线程,channelID就是为了标识进程中的线程。
连接的终止是ConnectDetach(),channel的结束则是ChannelDestroy()了。不过,一般服务器都是长久存在的,较少需要ChannelDestroy()。
服务器???
ReceiveId = MsgReceive(ChannelId, ReceiveBuffer, ReceiveBufLength, &MsgInfo);
(... 检查Buffer里的消息进行处理 ...)
MsgReply(RceeiveId, ReplyStatus, ReplyBuf, ReplyLen);
客户端???
MsgSend(ConnectionId, SendBuf, SendLen, ReplyBuf, ReplyLen);
(... 由OS将这个线程挂起 ...)
(... 当服务器MsgReply()后,OS 解除线程的阻塞状态, 客户端可以检查自己的 ReceiveBuf 看看应答结果 ...)
2)代理proxy,消息的一种特殊形式,适合事件通知,发送者不用和接收者交互
函数原型:
qnx_proxy_attach()创建proxy
Trigger()触发事件
3)信号signal,IPC的一种传统形式,通常用于一步IPC
shell环境下产生信号:kill,slay
进程内产生信号的C函数:kill(),raise()
相关函数:signal(),sigaction()
4 )跨网络IPC,通过虚电路机制实现(virtualcircuits),这种机制由QNX网络管理器完成。
相关函数:
qnx_vc_attach(0,qnx_name_locate()等
5 )基于信号灯的IPC
2 数据区与iov
??????? 除了用线性的缓冲区进行消息传递以外,QNX还提供了用iov_t来“汇集”数据,可以一次传送几块数据。如下图:客户端蓝色的Header同红色的databuf是两块不相邻的内存,但传递到服务器端的ReceiveBuffer里,就是连续的。在服务器端,要想得到原来databuf里的数据,只需要(ReceiveBuffer + sizeof(header))就可以了。(要注意数据结构对齐)
客户端?????
SETIOV(&iov[0], &header, sizeof(header));
SETIOV(&iov[1], databuf, datalen);
MsgSendvs(ConnectionId, iov, 2, Replybf, ReplyLen);??????????????????? ? ???????????????????????????????????????????????????
//"header" 与
"databuf" 是不连续的两块数据
服务器接收后,
"header"与
"databuf"被连续地存在ReceiveBuffer里。
? ? ? ? ? ? ? ???? ReceiveId = MsgReceive(ChannelId, ReceiveBuffer, ReceiveBufLength, &MsgInfo);
header = (struct header *)ReceiveBUffer;
databuf = (char *)((char *)header + sizeof(*header));
3 资源管理器
??????? 一个资源管理器基本上来说就是一个服务器。在QNX上,“资源”可以是一个硬件(硬件资源管理器其实就是我们常说的硬件驱动),也可以是一种服务,比如TCPIP网络服务,或者ntfs文件系统服务;“资源”甚至可以是一个文件(或者目录)。
??????? 因为一个资源管理器都是由一个路经名作为入口的,而POSIX又定义了对一个文件可进行的操作,所以QNX预定义了这些操作所需要传递的消息类型和数据格式。QNX提供的资源管理器框架大致可以分成4个部份:
? 1)? iofunc (iomsg) 层,提供了所有POSIX对文件可以进行的io操作 (sys/iofuncs.h, sys/iomsg.h)
QNX总结了34个对文件操作,基本上POSIX对文件的处理,都可以通过这34个操作进行。iofunc层的每一个回调函数,都有一个对应的数据结构供客户端和服务器端进行消息传递,在sys/iomsg.h里 | Connect Functions回调 | IO Functions回调 | open unlink rename mknod readlink link | read write close_ocb stat notify devctl unblock unblock mount pathconf lseek chmod chown utime openfd fdinfo lock space shutdown mmap msg umount dup close_dup lock_ocb unlock_ocb sync power |
比如 io_stat,
??? typedef union { ??????? struct _io_stat??? i; ??????? struct? stat?????? o; ??? } io_stat_t;
可以看到,io_stat是从客户端向服务器端发送一个 stauct _io_stat; 而服务器端返回的,直接就是一个 struct stat o; 这个struct stat, 就是 POSIX 的 stat() 函数取到的返回值。 ?
?? 2) resmgr层,提供了登记路径名,接收数据并分发给iofunc执行具体操作。iofunc 和 resmgr,是写一个资源管理器的基础。
??????? 如果 iofunc 层提供了各种回调函数的话,resmgr层就是那个“循环按收信息并调用iofunc”的那一层。例如:
??? resmgr_attach(….) ??? ctp = resmgr_context_alloc() ??? for (;;) { ??????? ctp = resmgr_block(ctp); ??????? resmgr_handler(ctp) ??? }
resmgr层和iofunc层给合,就可以搭建一个资源管理器。 ?
static int now_read(resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb)
{
iofunc_ocb_t *o = (iofunc_ocb_t *)ocb;
char nowstr[128];
time_t t;
int n;
/* 判断是不是文件 open 后第一次 read(),不是的话,回复0,这样客户端的 read()就会返回0,
* 以示读到了文件尾
*/
if (o->offset != 0) {
return 0;
}
/* 取当前时间,写入字符串 */
t = time(NULL);
n = strftime(nowstr, 128, "%Y-%m-%d %H:%M:%Sn", localtime(&t));
o->offset += n;
/* 把字符串返回给客户端 */
MsgReply(ctp->rcvid, n, nowstr, n);
/* 告诉 resmgr 层,我们已经做过Reply了,不要再reply客户端了 */
return _RESMGR_NOREPLY;
}
void main()
{
/* 建一个 dispatcher,这个可以想像就是一个接收数据的频道 */
dispatch = dispatch_create();
/* 资源管理器本身的一些参数,下面这个就是指定了资源管理器最多一次可以处理10个 iov_t */
memset( &res_attr, 0, sizeof( res_attr ) );
res_attr.nparts_max = 10;
res_attr.msg_max_size = 0;
/* io_attr 其实可以想像成一个文件相关的参数,比如读写权限等等 */
iofunc_attr_init(&io_attr, 0666 | S_IFCHR, 0, 0);
/* 初始化iofunc层回调 */
iofunc_func_init( _RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs );
io_funcs.read = now_read;
/* 建立起资源管理层,同时注册路径 */
rmgid = resmgr_attach(dispatch, &res_attr, "/dev/now", _FTYPE_ANY, 0, &connect_funcs,
&io_funcs,&io_attr);
/* 准备一个资源管理层的 context 以备使用 */
ctp = resmgr_context_alloc(dispatch);
while (1)
{
/* resmgr_block() 相当于做了一个 MsgReceive() */
ctp = resmgr_block(ctp);
/* 根据收到的信息,调用相应的回调程序 */
resmgr_handler(ctp);
}
}
//执行后会生产一个/dev/now 的文件
?? 3) dispatch层,在一些复杂的资源管理器里,“外来的消息传递”并不是唯一需要处理的。也有可能需要处理“脉冲”,或者有时候一个“信号”。dispatch层会主动识别不同的输入信息,然后转给不同的处理函数进行处理。
???????? 虽然大多数情况下,用iofunc层+resmgr层已经可以构建一个完整的资源管理器了,但是有时候资源管理器需要同时处理别的消息,这时就需要 dispatch 层了。dispatch可以处理其他形态的信息,除了可以用 resmgr_attch() 来挂接resmgr 和 iofunc以外,还可以做这些 (sys/dispatch.h):
int message_attach(dispatch_t *dpp, message_attr_t *attr, int low, int high,
int (*func)(message_context_t *ctp, int code, unsigned flags, void *handle),
void *handle);
有时候资源管理器在使用标准的 iomsg 以外,还想要定义自己的消息类型,那就会用到这个message_attach(). 这个函数意思是说,如果收到一个消息,它的消息类型在 low 和 high 之间的话,那就回调 func 来处理。
当然,要保证 low/high 不会与 iomsg 重叠,不然就会有歧义。在 iomsg.h 里已经定义了所有的 iomsg 在 _IO_BASE 和 _IO_MAX 之间,所以只要保证 low > _IO_MAX 就可以了。
?
pulse_attach()
int pulse_attach(dispatch_t *dpp, int flags, int code,
int (*func)(message_context_t *ctp, int code, unsigned flags, void *handle),
void *handle);
有许多管理硬件资源的管理器(驱动程序),除了提供iomsg消息,来对应客户端的io请求以外,也会很常见需要接收“脉冲”来处理中断 (InterruptAttachEvent)。这时,用 pulse_attach() 就可以把指定的脉冲号(code),绑定到回调函数 func 上。
select_attach()
int select_attach(void *dpp, select_attr_t *attr, int fd, unsigned flags,
int (*func)(select_context_t *ctp, int fd, unsigned flags, void *handle),
void *handle);
很多资源管理器,在处理客户端请求时,还需要向别的资源管理器发送一些请求。而很多时候这些请求可能不能立即返回结果,通常情况下,可以用 select() 来处理,但第一我们无法使用会阻塞的select(),因为我们是一个资源管理器的服务函数,如果被阻塞无法返回,就意味着我们无法处理客户端请求了;第二我们也无法用 select() 轮询,因为我们一旦返回,就会进入 等待客户端消息的阻塞状态,没有新消息来时,不会退出阻塞状态,也就没有机会再去轮询 select()了。
在使用 dispatch时,进行特殊的 *_attach() 挂接以后,只要把resmgr层的几个函数替换成dispath层的几个函数 就可以了,比如这样:
ctp = dispatch_context_alloc(dispatch);
while (1)
{
ctp = dispatch_block(ctp);
dispatch_handler(ctp);
}
dispath_block() 相当于阻塞并等待,而 dispatch_handle() 则根据不同的挂接,调用不同的回调函数进行处理。
另一个比较常用的dispatch函数是dispatch_create_channel()。有时候,你希望自己用 ChannelCreate()创建频道,而不是让dispatch_create()自动为你创建频道,就可以用这个函数。之所以需要自己创建频道,是因为有时候希望在频道上设一些特殊的标志(ChannelCreate() 的 flags)。
??? 4)thread pool层,提供了一个线程池管理,可以配置实现多个线程进行资源管理。
????????? 上面这些用 while (1) 来循环处理消息的资源管理器,明显都是“单线程”资源管理器,一共只有一个线程来处理客户端请求。对于一些需要频繁处理客户请求的资源管理器,自然会想到用“多线程”资源管理器。基本就是用几个线程来执行上面的 while(1)循环。
???????? 线程池(Thread Pool)就是用来实现这个的。使用起来也比较简单,先配置 poot_attr,然后创建并启动线程池。 ?
memset(&pool_attr, 0x00, sizeof pool_attr);
if(!(pool_attr.handle = dpp = dispatch_create())) {
perror("dispatch_create");
return EXIT_FAILURE;
}
pool_attr.context_alloc = dispatch_context_alloc;
pool_attr.block_func = dispatch_block;
pool_attr.handler_func = dispatch_handler;
pool_attr.context_free = dispatch_context_free;
pool_attr.lo_water = 2; //至少应该有多少线程需要在等待任务
pool_attr.hi_water = 5; //最多这些线程可以等待任务
pool_attr.increment = 2; //一次递增的线程数
pool_attr.maximum = 10; //线程池里最多可以建多少线程
tpp = thread_pool_create( &pool_attr, POOL_FLAG_EXIT_SELF)
thread_pool_start( tpp );
说明:线程池一旦启动,首先会创建5个线程,都在 dispatch_block()上等待接受任务。
当有一个请求来时,1号线程为其服务,假设这个服务线程在服务过程中,还需要向别的线程请求数据,一时回不来,那就还剩下4个线程等待任务。
如些再来两个请求,我们会变成3个线程在进行服务,2个线程等待任务的情形。
这时,当第4个请求来时,又一个线程去进行服务,这时只有1个线程在等待任务了,比lo_water少,所以线程池会自动再新建 2 个(increment) 线程,把他们放在等待任务队列中。所以这时我们有4个线程在服务,3个线程在等待。
如果服务线程无法回收,而新的请求又进来,导致等待线程数又低于lo_water的话,那么,线层池还会继续增加线程以保证有足够线程在等待服务,但是服务线程数与等待线程数的和,不会超过10(maximum).
假如在4个线程服务,3个线程等待的状态下,有2个服务线程结束了服务,它们会被还回线程池,线程池就会把这2个线程继续放入等待队列。也就是变有还有2个服务线程,5个等待线程的状态。
现在,如果又有1个线程结束服务,回归线程池了。如果把这个线程再放入等待队列,那就会有6 个线程等待服务,这个超过了 hi_water,所以线程池会结束这个线程。这样,我们就会有1个线程在服务,5个线程在等待的情形,线程总数下降到了6。
二 命令用法
打印所有的日志信息:slog2info??? (程序中的日志都是用slog2info写入的) 打印特定的日志信息:slog2info -m 12334?? (12334是程序中用sloginfo写的时候,自己定义的),这样就可以进显示12334的日志信息; 清除日志: slog2info -c??? (有时候日志信息太多,会先使用该命令将之前的日志信息清除,然后再使用sloginfo ,显示当前的日志信息)
实时显示日志信息:slog2info -w
slay? 进程名? :结束进程? (注意是进程名,不是进程号)
pindin :显示当前运行的所有进程名及进程号 pidin |grep 进程名???? :筛选我们想要的进程名
shutdown? :重启QNX系统 注意:当有守护程序时,倘若我们将守护去掉,shutdown后仍能重启守护的程序,但仅会启动这一次,关闭终端就不会再守护程序了,程序就不会再被重启了。 当我们测试程序时,不想让程序自动启动,方法: 修改守护程序配置文件,将进程名赋值为0? -----》mytest=0;? (ps: mytest=1;则是守护该进程) 或者将进程通过cp的方式重命名,然后将原可执行程序删除(因为不支持mv命令)
pfm? :打开资源管理器(文件系统)
ped?? :打开文本编辑器
passwd 用户名?????????? --->创建新用户,没有useradd命令, 键入passwd? 用户名后,会让输入密码。
参考文章:
QNX基础教程
qnx 设备驱动开发_QNX资源管理器_weixin_39603327的博客-CSDN博客
|