qnx驱动开发之编程基础

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的原生消息是基于客户端/服务器模型的。
qnx驱动开发之编程基础_第1张图片

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
• …
具体图解:
qnx驱动开发之编程基础_第2张图片
客户端连接服务器,发送消息:

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 来实现。

服务器客户端建立连接的程序框架示例:
qnx驱动开发之编程基础_第3张图片

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


qnx驱动开发之编程基础_第4张图片

iov_t结构详细:
typedef struct {
void *iov_base; //一个iov元素的首地址
size_t iov_len; //一个iov元素的长度
} iov_t;
一般将其作为一个数组使用:iov_t iovs [3];
如何使用一个IOV:
qnx驱动开发之编程基础_第5张图片
当它被发送或者接收时,这些分散部分将被看作是一个邻接的字节序列,这对于分散/集合 缓存 是一个很好的方法。与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:当内核把数据从客户端拷贝到服务端时,不会有任何特别的分组。只是将它们看作是一个没有任何分界的连续字节序列。
qnx驱动开发之编程基础_第6张图片
注:接收数据格式,是服务端自己定义。
notes:
因为内核不会强制对从客户端复制的数据进行特殊分组,服务器可以自由的以任何一种有意义的方式分组,来解释这些数据,不管客户端定义的分组。
我们理解了一个文件组成,它建立了一个IOV去将header读取到一个特殊的结构中,然后把剩余的数据以4K单位分块并存储在缓冲区。而不需要任何复制。
对于以上的header:我们是假设接收端知道有多少数据要接收。但在实际中,在我们处理消息之前,我们不知道要接收多少数据,就可以用以下方法:
qnx驱动开发之编程基础_第7张图片
notes:这个函数调用仅仅只是得到了header,然后服务端就去解析header,并且组织一个IOVs。该IOVs描述的是剩余的数据该如何存放,即服务端就知道了将要接收多少字节的数据。
内核不会写出超过在一个IOV钟具体指定缓存的末端。比如当接收到一个pluse,IOV的第一部分必须要足够大去接收pluse,否则,MsgReceive*()将会返回一个默认错误(Bad address)。
qnx驱动开发之编程基础_第8张图片
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状态
qnx驱动开发之编程基础_第9张图片

4.脉冲pluses
特性:

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

    使用方法:
    qnx驱动开发之编程基础_第10张图片
    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来发送。
qnx驱动开发之编程基础_第11张图片
notes:在events小结中将详细谈到。
pluse消息使用的架构:

4.1 pulse结构体 Pulse Structure
当我们接收pulse时,pluse 结构至少有以下成员。
qnx驱动开发之编程基础_第12张图片
code和value组成40bits发送码。code指定脉冲类型,value携带数据。
notes:pulse实际结构是一个union
union sigval {
int sival_int;
void *sival_ptr;
};
4.2 接收到pluse时的处理方法
qnx驱动开发之编程基础_第13张图片
因为我们接收到的是不同的pulse类型,我们用监测pulse code value的方法来判别接收到何种类型的pluse。
notes:如果在ChannelCreate()或者name_attach()调用时,你用了特殊的flags,你可能只收到内核pulse。

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

  • 能来自不同的地方
  • 接收能够以pulses,signals形式不阻塞InterruptWait()。。。
    qnx驱动开发之编程基础_第14张图片
    5.1 event delivery实例
    qnx驱动开发之编程基础_第15张图片
    1.客户端准备一个事件event并且和其他请求一起发送给服务端
    2.服务端接收,并存储在某个地方,然后回应客户端(我稍后处理这个事件),客户端继续运行
    3.当服务端完成事件后,将用MsgDeliverEvent (rcvid, &event)告诉客户端事件完成。然后客户端继续运行,可发送其他消息。
    5.2 the event
    event结构:
    qnx驱动开发之编程基础_第16张图片
    此结构包含了客户端想要的一切。
    客户端指定它想接收的event类型:
    qnx驱动开发之编程基础_第17张图片
    notes:
    SIGEV_INTR将引起一个符合InterruptWait() 函数来非阻塞。
    SIGEV_UNBLOCK不会阻塞线程,用于TimerTimeout() or timer_timeout().
    在SIGEV_结构单中,只有SIGEV_SIGNAL是POSIX,其余的都是QNX Neutrino独有的。
    5.3 pulse历程

    5.4 signal历程
    qnx驱动开发之编程基础_第18张图片
    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
    qnx驱动开发之编程基础_第19张图片
    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。
    例如:
    qnx驱动开发之编程基础_第20张图片
    我们想有一个服务端接收一个每隔1.2秒的时间维护消息,以致于它能够完成完整性检测。

    notes:

7.总结 conclusion
qnx驱动开发之编程基础_第21张图片

你可能感兴趣的:(编程,线程,通信,内核,驱动开发)