[请教]线程切换与优先级与执行时间

之前在论坛发了一些关于USB驱动的问题,多谢达人comquter等人的指导,已经参考例子写完一个注册到资源管理器使用的驱动。
同时也准备了USB设备端的测试程序进行了测试,发现了很多问题。

-------------------------课题描述的分割线--------------------------------

主机:使用QNX系统的普通台式机,CPU是P4 2~3G,内存是1G。收集设备端的数据并进行处理。
设备端:传感器,向主机传送数据的速度是640 byte/ms,最大可能到2~3 k/ms。

设备端需要每隔700~800us向主机发送512字节的数据,主机的USB驱动接收到一定量的数据(0.1M以上)并转存到数据处理程序的空间,同时程序也在处理之前收到的数据。

因此,理想的状态是设备端每隔700~800us向主机发送512字节的数据,主机的USB驱动也相应地每隔700~800us接收,等达到要求的大小(比如0.1M)的时候转存到数据处理程序的空间,然后继续接收数据,其它的时间都用来处理数据。这期间从设备端不间断地传送数据。

-------------------------理想与现实的分割线-----------------------

由于设备端还未完成,暂时写了测试程序代替硬件,效果是最快每隔125us就可以传送512字节(主机的send packet发送及时的情况下)。
主机端的数据处理程序也是半成品,处理0.1M的数据需要60~70ms。

测试程序使用了双线程,线程read调用USB驱动收集并转存数据,线程app处理数据并有2个buffer,这样数据的转存和处理就不会有冲突。

但是,系统测试的结果出人意料,QNX的多线程没有充分体现。。。

--------------------------结果的分割线--------------------------

测试单独的线程的时候,read需要大概45ms收集并转存0.1M的数据,app需要60~70ms处理相同大小的数据。

但是一起运行的时候,不断调整两个线程的优先级,总共的时间在110到130之间浮动。
糟糕的是最快的线程切换也是要隔上3~4ms,完全做不到实时传输数据。
更糟糕的是有些时候主机从设备端的数据读取被中断了57ms之多。。。也就是完全没有线程的切换。。。

还有,USB驱动得到指定大小(0.1M)的数据后,向程序的buffer里转存时居然需要7ms。。。要知道即使是用for循环直接复制也就是us级的消耗。。。而且还必须得用 resmgr_msgwrite 这个函数,直接复制的话会被系统关掉驱动。。。

--------------------------求助的分割线---------------------------

怎样才能实现理想中及时切换运行呢?
还是说这和USB的传送方式也有关?目前是USB 2.0 high-speed 的bulk 传送方式。

这帖子又臭又长,感谢您能看到这里,更加感谢您的帮助。

你的系统还是别用多线程了,整一轮询就OK了。
线程切换时间到达ms级别应该是不可能的,仔细检查下程序吧。

另外,你是不是动态分配内存了??如果是,那么现在的结果很正常。

还有,线程之间为什么非要用消息呢??

laris,感谢你的回复。

轮询的话,要用到计时器的吧,正在查询相关的资料。

印象中没有动态分配内存,都是申请的固定大小空间。至于系统管理的时候怎么做的就不清楚了。。。
为什么说动态分配内存会导致目前的情况呢?

驱动注册到了资源管理器,和应用程序之间的数据交换就用了消息的方式。内核对这方面挺严格的样子,不允许直接复制数据。

顺便问一下,使用函数 ClockPeriod 可以改变系统的 tick 值吧,这样是应该可以让线程更加频繁地切换?

动态分配内存是需要操作系统做很多工作的,比如Linux下的Buddy系统什么的,
操作系统必须使用很谨慎的算法来给你动态开内存,不然必然出现内存碎片的问题。

检查下自己的程序,看看是否有临时变量数组这样的玩意。

一般轮询不需要定时器啊,整个while死循环就行了。
使用定时器的轮询的好处是省电,你应该不会在乎那点电费吧。。。呵呵

tick值只影响线程切换的时间精度,但是并不影响切换的速度。
把它改小了,你的系统处理能力会下降的。

多谢提醒,我会注意动态分配内存的问题。

因为要配合外部设备端,主机需要每隔一定的时间(比如说1ms)去读取512字节的数据,同时app还要处理上一个循环积攒的几千k字节数据。
所以,切换的时机很重要。
轮询的话,就不好处理了。。。

改变切换的时间精度虽然会影响系统性能,但是细化的切换时机(比如100ns)能够比较好地适应外部设备的数据传输。

想像一下,你现在的系统大致有四个有关线程?

  1. 代替硬件的测试线程(A)

  2. USB驱动收集0.1M然后resmgr_msgwrite()到用户程序的线程 (B)

  3. 处理程序里的read()线程(C)

  4. 处理程序里的处理线程(D)

我不知道 B 现在是如何工作的,如果是标准的USB驱动的话,那它应该有足够高的优先级,如果不是的话,试试把B设到21

C 设到20,然后D设到18(其实D设在10上应该也没什么关系,保险起见:D)。

我不知道 A 是不是也跑在QNX上,如果是的话,A对整个系统的影响也会比较大。

感谢回复。

硬件部分是真实存在的,最快每隔125us就能准备好送往主机的一个512字节数据包。所以没有代替硬件的测试线程(A)。

(B)和(C)的关系以前没有想到过。。。
USB驱动注册到了资源管理器,所以程序使用 read( fd, &buf, 512 ) 的时候,是通过资源管理器直接调用驱动里面的相应函数,还是依靠驱动线程呢?
仔细想想的话,应该 xtang 兄是对的,也就是说是资源管理器向驱动线程发送消息,驱动线程调用自己的某函数完成任务后返回一个消息,这里用到了 resmgr_msgwrite()。

因为驱动是自己写的,运行的时候优先级是系统默认值。
多谢提醒,接下来就尝试改变驱动的优先级。

resmgr_msgwrite() 这个函数可能不是问题了。

开始的时候,驱动是在收到几百个512字节的数据包之后才向调用程序回写数据,也就是说 resmgr_msgwrite() 这个函数一下子要写 接近 M 级的数据,可能数据过大导致效率低下。
而官方对这个函数的解释是:
use MsgWrite() to write messages in small pieces
( 函数 resmgr_msgwrite() 是调用函数 MsgWrite() )

后来改写为接到一个512字节的数据包就回写之后,时间明显减少。
(其实DDK里面的参考驱动就是这么写的,只不过我之前自作聪明改掉了。。。)

轮询的好处在于可以避免使用系统调用,提高速度。
坏处在于扩展性不大好。

你现在每512字节回送一下也是有弊端的,整个系统的处理能力会下降。
当然,如果处理能力足够,那么无所谓啦。

回laris:
因为系统对于时间方面要求比较严格,不得不依靠计时器,或者是很好的任务安排,使得主机和设备端同步。

回xtang:
驱动程序的优先级修改过了,确实有影响。

但是还有问题。就是不论是 计时器 还是修改 时间片,线程的切换时间都不是绝对的,仍然会有不和谐事件。。。
譬如说,程序切换设定为 1ms 执行一次,但是仍然有 3、4ms 才执行一次的情况(不排除系统运行其他不相关程序的可能)

我没明白你说的“程序切换设定为1ms“是什么意思。

我想像中你的系统是这样的,包括了驱动程序线程B(用于从USB取得数据,并回传给C),和read()中的C;还有一个处理线程D。

数据流动是这样的:

1)B负责从USB收数据,并msgwrite()到C的Buffer中,当数据“满”了时,B通过MsgReply()让C激活。

2)C从read()返回后,通知线程D可以开始处理了,并把收满数据的Buffer给D。同时C,取得另一块 Buffer,再去 read()

3)D 对 C 传来的 Buffer进行处理,处理完成后;如果C有下一个Buffer已经ready了,D可以直接处理下一Buffer;如果没有,则D进入等待状态。

B需要有较高的优先级(当心不要高过20)以便适时地把数据收进来。(取决于B有多少Buffer可以存数据)

C与D之间,用个condvar保护的link-list就可以了。如果按你说的,read()要45ms, app要70ms,那么两个buffer是不够的,起码得3个0.1M的buffer轮转才行。考虑到系统别的开销,4个0.1M的Buffer才会比较放心吧。

你说的“及时切换”到底是什么?

1ms就是时间片的长度,这样线程切换就是以 1ms 为单位了。

系统的大概流程如你所说。
为什么线程B的优先级不要超过20呢?
之前的建议,我记得是设定为21吧?。。。

不好意思,我一直没有说清楚。

实际情况就是,因为是实时的画面处理,在一个周期里,驱动每隔1ms从设备端读取数据,并从不间断;同时数据处理程序在处理上一张画面的数据,当处理完成后等待下一个周期的开始。
这期间需要不断地在多个线程间来回切换。

因此才搞得这么麻烦。。。

PS:关于“condvar保护的link-list“,我没查到有用的资料,能不能解释一下呢?谢谢。

如果是你说的这样的话,设置个FIFO 形式的Buffer不就完了么。
反正你的处理程序是处理上一帧数据,完全可以直接放在收数的代码后边,一个程序搞定。收数程序将收到的数据往FIFO填,处理程序首先查询FIFO是否有上一真数据,有的话就处理。

这样程序结构简单,而且也没有线程切换之类的事情。

不要认为多线程就能让你真的多个CPU出来,你如果是多核的机器,可能多线程快些,但是你那个机器就是P4。把工作都放在一个线程如果超时,你分成多个线程一样超时。

多谢指教。

如果程序按照这样执行的话,处理数据的时候就不能通过驱动接收数据的吧?
但是设备端那边不能等。。。

我知道所谓多线程并不会真的快多少,只是想在接收数据的间隙处理数据,通过合理的任务安排提高效率。
(毕竟接收数据是每隔1ms,而真正的接收数据的动作很快,几us的样子)

你的系统,与线程/进程切换时间、时间片关系不大。不过我觉得你这里有些理解错误,先把这个说明一下。

QNX的标准系统时钟,是1ms;标准时间片是 4 x system tick,也说是说,默认是4ms。换句话说,如果“什么事情都不发生”的话,一个线程最多可以占用4msCPU。在4ms后,系统重新调度,这时,如果当前线程依然是最高优先级的话,它依然占用CPU…

但这只是理论情况,通常在QNX里一个线程消耗完一个时间片的情形是比较少见的。因为线程可能在执行中需要进行消息传递(别忘了,即使做个 printf() 也要消息传递的),要消息传递就会进入阻塞状态,进入了阻塞状态就意味着CPU会被 别的线程占用。

还有一种就是,如果有更高优先级的进程需要取得CPU的时候。比如,你的数据处理线程D正在CPU上完全计算中,即使它占着CPU不放,但如果B读数据的时间到了,因为B的优先级高,B还是会立刻抢占CPU的,而不是等D的时间片到了再切换。

所以你的系统,是优先级高低的问题,不是(也不应该是)定时切换的问题。

我的理解,B是一个进程,C和D是另一个进程,对吗?B是“每隔1ms从io-usb那里读取数据”的那个线程。我不明白B为什么需要每1ms去读取数据,感觉上B应该可以用什么usbd_ 函数等待数据的到来,而不是每隔1ms去读。

即使是每隔1ms去读,现在的BCD里,B是最高优先级的,所以C和D应该是没办法延迟B的执行("有时会隔3、4ms才读)。

这里有几个问题,B的“每隔1ms去读“,是你自己开的时钟吗?时钟的Event是用的Pulse吗?Pulse里的优先级有没有设到20 ?

按你所说,读满 0.1MB数据需要45ms的话,处理需要70ms的话,一个周期至少就要115ms。也就是说115ms内,数据从硬件传到处理程序,并完成显示。就象laris说的,并不是多用几个线程你就可以提高这个速度的。你的一个CPU,要处理这些工作,就需要这些时间。这就是我们所说的CPU Bound (CPU限制)。

你可以做到的(用多线程的好处),是在处理的同时,把数据收进来。这个并不能减少CPU时间。本来从开始处理一个0.1M,到显示完成,只要70ms。但现在因为中间不断地要切换出去,读取数据,所以处理一个画面会需要更多的,比如115ms,不过好处是完了后,立刻可以处理下一个画面而不用等待(因为下一个0.1m已经收齐了)。

多谢回复。

仔细想想,确实如你所说,应该关注的是优先级的问题。
之前对于时间的问题过于执著了。。。


是的,B是驱动进程的线程,C和D是数据处理进程的线程。
驱动里面,实际上是用的函数 usbd_io( ) 注册了callback 函数,因此每当USB发送或者接收一个数据包就会调用 callback 函数并同时准备下一次数据传送,而数据传送的时间间隔大概是 1ms,所以我一直说每隔 1ms 去读取数据。

很抱歉一直没有解释清楚。。。


我也尝试过使用时钟,在间隔 1ms 的时钟线程里调用驱动读取数据,设置部分代码如下:

 chid = ChannelCreate(0);
 event.sigev_notify = SIGEV_PULSE;	
 event.sigev_coid = ConnectAttach(ND_LOCAL_NODE,0,
                              chid,_NTO_SIDE_CHANNEL,0);
 event.sigev_priority = getprio( 0 );
 event.sigev_code = MY_PULSE_CODE;
 timer_create(CLOCK_REALTIME, &event, &timer_id);

 itime.it_value.tv_sec = 0; 
 itime.it_value.tv_nsec = 1000000;	  
 itime.it_interval.tv_sec = 0; 
 itime.it_interval.tv_nsec = 1000000;
 timer_settime(timer_id, 0, &itime, NULL);

测试程序的时候我在每个线程的开始和结束都使用了函数 clock_gettime( ),这样通过打印出来这些时间来观察线程的执行流程和时间。
但是结果不能让人满意,并不是预想那样地严格按照时间间隔。

不过我想问题可能还是在于我的程序,接下来把设备端的软硬件搞定之后再着手处理问题。

嗯,感谢你的解释。
其实如果可以的话,我也不想搞来回切换。。。
就是设备端那边的数据读取不能停。现在也在考虑设备端使用一个硬件FIFO的可行性。

如果你强调接受数据的操作不能停的话,即使你使用多线程又如何?
接收数据45ms,处理数据70ms.也就是表示数据的进入速度肯定大于输出速度,所以即使使用任何方法,总会有一个时刻让你的内存塞到爆掉。

首先想想如何提高处理速度吧。或者考虑下你的数据多长周期来一次。
如果数据是不间断的连续送来,基本上这个系统是失败的。
如果数据之间的间隔大于115ms,那还有得搞。

刚看了看前面的帖子,基本上不到2ms就有0.1M数据了。。。。
而你光收数就要45ms。。。。。。。。

系统设计就有问题啊。。。。。

我还有个疑问,为什么接收0.1M数据要45ms??
根据我的经验,这个时间太长了点。