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, ¶m);
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()发送。
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*() 回应客户端。
4.脉冲pluses
特性:
快速
使用方法:
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是一种消息通知形式。
6.Time
QNX® Neutrino®的Time概念:
notes:
在传统的x86PC上,使用的是主板上的时钟。它能够生成1.1931816 MHz的中断。
6.1 Ticksize 节拍
即所有定时都将基于这个规定,分别不会低于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: