qnx驱动开发之编程基础

来源:互联网 发布:知乎 新人 西部世界 编辑:程序博客网 时间:2024/06/05 14:18

qnx驱动开发之编程基础 
包括:线程与同步;QNX微内核进程间通信IPC; QNX时间相关 
主题: 
1.线程 
2.同步 
3.消息传递message 
4.脉冲pulses 
5.事件传送event 
6.时间 
7.总结

1.线程 
1.1 进程与线程 
线程在进程中运行 
a.一个进程中至少有一个线程 
b.在一个进程中的线程共享该进程中所有资源 
这里写图片描述

1.2 如何创建一个线程 
pthread_create (pthread_t tid, pthread_attr_t *attr, void (func) (void ), void *arg); 
用例: 
pthread_create (&tid, &attr, &tfunc, &arg);

1.tid:用于存储线程ID 
2.arrt:用于设置线程的特性,如运行的优先级 
3.tfunc:该线程的运行函数 
4.arg:为传递给tfunc的各种参数

1.3 使用线程的attributes来设置线程优先级: 
pthread_attr_t attr; 
struct sched_param param; 
pthread_attr_init(&attr);// init to defaults 
pthread_attr_setinheritsched (&attr, 
PTHREAD_EXPLICIT_SCHED); 
param.sched_priority = 15; 
pthread_attr_setschedparam (&attr, &param); 
pthread_attr_setschedpolicy (&attr, 
SCHED_NOCHANGE); 
pthread_create (NULL, &attr, t_func, &t_args);

2.同步 
2.1 资源 
一个进程中的多线程资源共享情况 
共享资源: 
1.定时器 timers 
2.频道 channels 
3.链接 connections 
4.内存存取 memory access 
5.文件描述符 file pointer/descriptors 
6.信号处理 signal handlers

不共享资源: 
1.栈 stack 
2.寄存器 registers 
3.对称多内核的cpu掩码 cpu mask for SMP 
4.信号掩码 signal mask

2.2 问题 
多线程的新问题 
共享内存区: 
1.多线程间写:可能会重复写彼此的资源 
2.多线程间读:不知道数据什么时候稳定或者有效 
3.线程需要有本地存储,即使这个存储为全局的

解决方案:同步 
1.互斥量 mutexes 
2.一次运行 once control 
3.读写锁 rwlocks 
4.睡眠 sleepon 
5.condvars

2.3互斥 mutual exclusion 
互斥意味着只有一个线程执行以下内容: 
1.一次只有一个线程能进入临界段 
2.一次只有一个线程能存取某段特殊数据 
3.一次只有一个线程能访问硬件

posix提供以下调用: 
– administration 
pthread_mutex_init (pthread_mutex_t , pthread_mutexattr_t ); 
pthread_mutex_destroy (pthread_mutex_t *); 
– usage 
pthread_mutex_lock (pthread_mutex_t *); 
pthread_mutex_trylock (pthread_mutex_t *); 
pthread_mutex_unlock (pthread_mutex_t *);

一个简单的用例: 
pthread_mutex_t myMutex; //互斥量的创建 
init () //互斥量的初始化 

… 
// create the mutex for use 
pthread_mutex_init (&myMutex, NULL); 
… 

thread_func () //线程函数过程 

… 
// obtain the mutex, wait if necessary 
pthread_mutex_lock (&myMutex); 
// critical data manipulation area 
// end of critical region, release mutex 
pthread_mutex_unlock (&myMutex); 
… 

cleanup ()//互斥量的销毁 

pthread_mutex_destroy (&myMutex); 

注意:typedef struct { 
pthread_mutex_t mutex; 
char dataArea [64]; 
int flags; 
} LockingDataStructure_t; 
在这个结构中,这个函数pthread_mutex_destroy (&myMutex);经常被用到。 
如果这个结构是动态的创建与释放,在这个结构释放之前,Mutex应该被销毁。 
当一个进程dies后,任何的Mutex锁定都会被该进程自动地解锁并销毁。

3. 消息传递 
3.1 QNX® Neutrino® RTOS的原生消息是基于客户端/服务器模型的。 
这里写图片描述

1.客户端给服务器发送请求,阻塞 
2.服务器接收,并且处理消息 
3.服务器回应客户端,客户端从服务器接收到回应,就继续运行

消息传送场景的具体内容: 
– 服务器Server: 
• 创建一个频道 creates a channel (ChannelCreate()) 
• 等待消息waits for a message (MsgReceive())* 
• 执行消息处理流程performs processing 
• 给客户端发送回应sends reply (MsgReply())* 
• 继续运行goes back for more 
– Client: 
•链接服务器的频道,得到一个链接connection attaches to channel (ConnectAttach()) 
• 发送消息 sends message (MsgSend())* 
•等待服务器回应 ,接收回应 processes reply 
• … 
具体图解: 
这里写图片描述
客户端连接服务器,发送消息: 
这里写图片描述
coid = ConnectAttach (nd, pid, chid, index, flags) 
MsgSend (coid, &sendmsg, sbytes, &replymsg, rbytes)

. nd:节点描述符 noid descriptor (分布式节点)不是分布式就默认 ND_LOCAL_NODE 
. pid:进程ID process id 
. chid:频道ID channel id 
. nd,pid,chid的信息都可以从进程管理器process manager中得到 
尽管connection ID coid是一个文件描述符并且一个文件描述符也可以是coid,但是不可以使用I/O函数。 (e.g. you wouldn’t do write(coid, …)). 原因是:如果用fd = open(…)来创建一个connection,资源管理器进程(the server)就知道了这个文件描述符。那么资源管理器就不知道任何用ConnectAttach()创建的connection。 
注意: 因为程序经常默认文件描述符0, 1 and 2 是stdin, stdout, andstderr, 对于connection IDs用数字来表示比用文件描述符好。你可以用 ConnectAttach() 中的标志flags_NTO_SIDE_CHANNEL 来实现。

服务器客户端建立连接的程序框架示例: 
这里写图片描述

3.2 使用IOVs 
当你需要将多个缓冲(数组)用一个消息发送时,有两个方案: 
1. 使用多次memcpy()将多个数组组合成一个数组,再使用MsgSend()发送。 
2. 使用IOVs,再使用MsgSendv()发送。

1.msgsend
这里写图片描述

iov_t结构详细: 
typedef struct { 
void *iov_base; //一个iov元素的首地址 
size_t iov_len; //一个iov元素的长度 
} iov_t; 
一般将其作为一个数组使用:iov_t iovs [3]; 
如何使用一个IOV: 
这里写图片描述
当它被发送或者接收时,这些分散部分将被看作是一个邻接的字节序列,这对于分散/集合 缓存 是一个很好的方法。与IOVs相关的消息函数名都附带一个”v” 比如(MsgReceivev/MsgReadv/MsgReplyv/MsgSendv/MsgSendsv/MsgSendvs/MsgWritev) 
使用IOV的主要的好处是:可以解决发送或接收一个以多个分散的部分组成的消息。。如a header and several blocks of data

SETIOV()宏展开: 
、、#define SETIOV(_iov, _addr, _len) \ 
((_iov) -> iov_base = (void *)(_addr), (_iov) -> iov_len = (_len)) 
这里写图片描述
notes:发送时,不会将header和message拷贝到一个线性区然后发送,而是使用IOVs去通知内核它应该使用由IOVs描述的不同数据片段。 
这里写图片描述
notes:当内核把数据从客户端拷贝到服务端时,不会有任何特别的分组。只是将它们看作是一个没有任何分界的连续字节序列。 
这里写图片描述
注:接收数据格式,是服务端自己定义。 
notes: 
因为内核不会强制对从客户端复制的数据进行特殊分组,服务器可以自由的以任何一种有意义的方式分组,来解释这些数据,不管客户端定义的分组。 
我们理解了一个文件组成,它建立了一个IOV去将header读取到一个特殊的结构中,然后把剩余的数据以4K单位分块并存储在缓冲区。而不需要任何复制。 
对于以上的header:我们是假设接收端知道有多少数据要接收。但在实际中,在我们处理消息之前,我们不知道要接收多少数据,就可以用以下方法: 
这里写图片描述
notes:这个函数调用仅仅只是得到了header,然后服务端就去解析header,并且组织一个IOVs。该IOVs描述的是剩余的数据该如何存放,即服务端就知道了将要接收多少字节的数据。 
内核不会写出超过在一个IOV钟具体指定缓存的末端。比如当接收到一个pluse,IOV的第一部分必须要足够大去接收pluse,否则,MsgReceive*()将会返回一个默认错误(Bad address)。 
这里写图片描述
notes: MsgRead*()函数调用继续把数据从客户端拷贝到服务端,从起始地址开始拷贝。(该例中是12,因为该例的header的大小是12bytes),因为服务端已经提前计算出缓存的地址,所以剩余的消息可以接收到。 
被读取的线程如果还没有得到回应,它就必须处于REPLY_BLOCKED状态。 
你可以尽你的需要去多次使用MsgRead*() 函数来处理消息。 
例如:你可以调用MsgRead*()(with an offset of 12 bytes)来接收第一个4k数据。等你处理完这 4k数据后,你再次调用MsgRead*() (with an offset of 4K + 12 bytes)来接收第二个4k数据。以此类推,直到你读取完所有数据。另外,如果你不需要读取所有客户端提供的数据,你不需要做任何处理,仅仅是忽略剩余的数据,然后MsgReply*() 回应客户端。

3.3状态 
这里写图片描述

4.脉冲pluses 
特性:

  • 小,非阻塞消息传递 
    32位有效值 
    8位编码(负值保留)即只有7位可用 (一般负值用于系统pluse)
  • 快速

    使用方法: 
    这里写图片描述
    pulses的接收和其他消息message一样,都是使用 MsgReceive*()函数调用。此函数返回0表明是pluse,否则是message。 
    notes:MsgSendPulse()以一种非阻塞方式发送40位有效值信息。 
    priority优先级参数:可以允许你指定pluse的优先级。 
    code:范围是_PULSE_CODE_MINAVAIL to_PULSE_CODE_MAXAVAIL,负值被QSS保留。 
    返回值:0:你不能使用MsgReply*() ,因为表明它是pluse,不需要回应。即它是单向的消息。

更常用的是:将pluse作为一个事件event来发送。 
这里写图片描述
notes:在events小结中将详细谈到。 
pluse消息使用的架构: 
这里写图片描述
4.1 pulse结构体 Pulse Structure 
当我们接收pulse时,pluse 结构至少有以下成员。 
这里写图片描述
code和value组成40bits发送码。code指定脉冲类型,value携带数据。 
notes:pulse实际结构是一个union 
union sigval { 
int sival_int; 
void *sival_ptr; 
}; 
4.2 接收到pluse时的处理方法 
这里写图片描述
因为我们接收到的是不同的pulse类型,我们用监测pulse code value的方法来判别接收到何种类型的pluse。 
notes:如果在ChannelCreate()或者name_attach()调用时,你用了特殊的flags,你可能只收到内核pulse。

5.事件 Event Delivery 
event是一种消息通知形式。

  • 能来自不同的地方
  • 接收能够以pulses,signals形式不阻塞InterruptWait()。。。 
    这里写图片描述 
    5.1 event delivery实例 
    这里写图片描述 
    1.客户端准备一个事件event并且和其他请求一起发送给服务端 
    2.服务端接收,并存储在某个地方,然后回应客户端(我稍后处理这个事件),客户端继续运行 
    3.当服务端完成事件后,将用MsgDeliverEvent (rcvid, &event)告诉客户端事件完成。然后客户端继续运行,可发送其他消息。 
    5.2 the event 
    event结构: 
    这里写图片描述 
    此结构包含了客户端想要的一切。 
    客户端指定它想接收的event类型: 
    这里写图片描述 
    notes: 
    SIGEV_INTR将引起一个符合InterruptWait() 函数来非阻塞。 
    SIGEV_UNBLOCK不会阻塞线程,用于TimerTimeout() or timer_timeout(). 
    在SIGEV_结构单中,只有SIGEV_SIGNAL是POSIX,其余的都是QNX Neutrino独有的。 
    5.3 pulse历程 
    这里写图片描述 
    5.4 signal历程 
    这里写图片描述 
    notes: 
    The code (event.sigev_code) must be in the range SI_MINAVAIL to 
    SI_MAXAVAIL from sys/siginfo.h.

6.Time 
QNX® Neutrino®的Time概念: 
这里写图片描述
notes: 
在传统的x86PC上,使用的是主板上的时钟。它能够生成1.1931816 MHz的中断。 
6.1 Ticksize 节拍

  • 如果处理器速度> 40MHz,默认Ticksize 为1ms
  • 如果处理器速度>=40MHz,默认Ticksize 为10ms
  • 即所有定时都将基于这个规定,分别不会低于1ms或10ms。 
    notes:该时钟通常不能被用于精确到1ms定时的编程中,如果要用一般会选用更低的时钟。例如:on IBM PC hardware, you willactually get 0.999847ms

    在编程中,我们可以改变这个定时值tick size。 
    notes:QNX Neutrino基本不能提供精确2ms的定时,它只会很接近有效时钟周期。因为这个时钟是来源于晶振或者被整除得到。 
    6.2 如何使用定时器 using timers 
    这里写图片描述
    6.3 如何设置定时器 setting a tiemr 
    要设置一个定时器,进程该这样设置: 
    1.要搞清是什么类型的timer 
    • periodic 周期的 
    • one-shot 一次性的 
    2.timer anchor 时间参考点 
    • absolute 绝对的 
    • relative 相对的 
    3.触发器下的event发送 
    • fill in an EVENT structure 
    notes:如果准备使用电源管理,当你们不使用它们时,要记得关掉periodic timers。 
    例如: 
    这里写图片描述 
    我们想有一个服务端接收一个每隔1.2秒的时间维护消息,以致于它能够完成完整性检测。 
    这里写图片描述
    notes:

7.总结 conclusion 
这里写图片描述

原创粉丝点击