I want to be a complete engineer - technical genius and sensitive humanist all in one!

Monday, July 13, 2009

Linux 线程模型LinuxThreads 和 NPTL的比较

目前手头的工作需要用到linux线程编程,所以就草草的查找了点关于线程方面的资料,针对目前linux下面最主要的两大线程库做一简要的描述,以便将来查阅。
当 Linux 最初开发时,在内核中并不能真正支持线程。但是它的确可以通过 clone() 系统调用将进程作为可调度的实体。这个调用创建了调用进程(calling process)的一个拷贝,这个拷贝与调用进程共享相同的地址空间。LinuxThreads 项目使用这个调用来完全在用户空间模拟对线程的支持。不幸的是,这种方法有一些缺点,尤其是在信号处理、调度和进程间同步原语方面都存在问题。另外,这个 线程模型也不符合 POSIX 的要求。

LinuxThreads 设计细节

线程 将应用程序划分成一个或多个同时运行的任务。线程与传统的多任务进程 之间的区别在于:线程共享的是单个进程的状态信息,并会直接共享内存和其他资源。同一个进程中线程之间的上下文切换通常要比进程之间的上下文切换速度更 快。因此,多线程程序的优点就是它可以比多进程应用程序的执行速度更快。另外,使用线程我们可以实现并行处理。这些相对于基于进程的方法所具有的优点推动 了 LinuxThreads 的实现。

LinuxThreads 最初的设计相信相关进程之间的上下文切换速度很快,因此每个内核线程足以处理很多相关的用户级线程。这就导致了一对一 线程模型的革命。

让我们来回顾一下 LinuxThreads 设计细节的一些基本理念:

  • LinuxThreads 非常出名的一个特性就是管理线程(manager thread)。管理线程可以满足以下要求:

    • 系统必须能够响应终止信号并杀死整个进程。
    • 以堆栈形式使用的内存回收必须在线程完成之后进行。因此,线程无法自行完成这个过程。
    • 终止线程必须进行等待,这样它们才不会进入僵尸状态。
    • 线程本地数据的回收需要对所有线程进行遍历;这必须由管理线程来进行。
    • 如果主线程需要调用 pthread_exit(),那么这个线程就无法结束。主线程要进入睡眠状态,而管理线程的工作就是在所有线程都被杀死之后来唤醒这个主线程。

  • 为了维护线程本地数据和内存,LinuxThreads 使用了进程地址空间的高位内存(就在堆栈地址之下)。

  • 原语的同步是使用信号 来实现的。例如,线程会一直阻塞,直到被信号唤醒为止。

  • 在克隆系统的最初设计之下,LinuxThreads 将每个线程都是作为一个具有惟一进程 ID 的进程实现的。

  • 终止信号可以杀死所有的线程。LinuxThreads 接收到终止信号之后,管理线程就会使用相同的信号杀死所有其他线程(进程)。

  • 根据 LinuxThreads 的设计,如果一个异步信号被发送了,那么管理线程就会将这个信号发送给一个线程。如果这个线程现在阻塞了这个信号,那么这个信号也就会被挂起。这是因为管理线程无法将这个信号发送给进程;相反,每个线程都是作为一个进程在执行。

  • 线程之间的调度是由内核调度器来处理的。

LinuxThreads 及其局限性

LinuxThreads 的设计通常都可以很好地工作;但是在压力很大的应用程序中,它的性能、可伸缩性和可用性都会存在问题。下面让我们来看一下 LinuxThreads 设计的一些局限性:

  • 它使用管理线程来创建线程,并对每个进程所拥有的所有线程进行协调。这增加了创建和销毁线程所需要的开销。

  • 由于它是围绕一个管理线程来设计的,因此会导致很多的上下文切换的开销,这可能会妨碍系统的可伸缩性和性能。

  • 由于管理线程只能在一个 CPU 上运行,因此所执行的同步操作在 SMP 或 NUMA 系统上可能会产生可伸缩性的问题。

  • 由于线程的管理方式,以及每个线程都使用了一个不同的进程 ID,因此 LinuxThreads 与其他与 POSIX 相关的线程库并不兼容。

  • 信号用来实现同步原语,这会影响操作的响应时间。另外,将信号发送到主进程的概念也并不存在。因此,这并不遵守 POSIX 中处理信号的方法。

  • LinuxThreads 中对信号的处理是按照每线程的原则建立的,而不是按照每进程的原则建立的,这是因为每个线程都有一个独立的进程 ID。由于信号被发送给了一个专用的线程,因此信号是串行化的 —— 也就是说,信号是透过这个线程再传递给其他线程的。这与 POSIX 标准对线程进行并行处理的要求形成了鲜明的对比。例如,在 LinuxThreads 中,通过 kill() 所发送的信号被传递到一些单独的线程,而不是集中整体进行处理。这意味着如果有线程阻塞了这个信号,那么 LinuxThreads 就只能对这个线程进行排队,并在线程开放这个信号时在执行处理,而不是像其他没有阻塞信号的线程中一样立即处理这个信号。

  • 由于 LinuxThreads 中的每个线程都是一个进程,因此用户和组 ID 的信息可能对单个进程中的所有线程来说都不是通用的。例如,一个多线程的 setuid()/setgid() 进程对于不同的线程来说可能都是不同的。

  • 有一些情况下,所创建的多线程核心转储中并没有包含所有的线程信息。同样,这种行为也是每个线程都是一个进程这个事实所导致的结果。如果任何线程 发生了问题,我们在系统的核心文件中只能看到这个线程的信息。不过,这种行为主要适用于早期版本的 LinuxThreads 实现。

  • 由于每个线程都是一个单独的进程,因此 /proc 目录中会充满众多的进程项,而这实际上应该是线程。

  • 由于每个线程都是一个进程,因此对每个应用程序只能创建有限数目的线程。例如,在 IA32 系统上,可用进程总数 —— 也就是可以创建的线程总数 —— 是 4,090。

  • 由于计算线程本地数据的方法是基于堆栈地址的位置的,因此对于这些数据的访问速度都很慢。另外一个缺点是用户无法可信地指定堆栈的大小,因为用户可能会意外地将堆栈地址映射到本来要为其他目的所使用的区域上了。按需增长(grow on demand) 的概念(也称为浮动堆栈 的概念)是在 2.4.10 版本的 Linux 内核中实现的。在此之前,LinuxThreads 使用的是固定堆栈。

关于 NPTL

NPTL,或称为 Native POSIX Thread Library,是 Linux 线程的一个新实现,它克服了 LinuxThreads 的缺点,同时也符合 POSIX 的需求。与 LinuxThreads 相比,它在性能和稳定性方面都提供了重大的改进。与 LinuxThreads 一样,NPTL 也实现了一对一的模型。

Ulrich Drepper 和 Ingo Molnar 是 Red Hat 参与 NPTL 设计的两名员工。他们的总体设计目标如下:

  • 这个新线程库应该兼容 POSIX 标准。

  • 这个线程实现应该在具有很多处理器的系统上也能很好地工作。

  • 为一小段任务创建新线程应该具有很低的启动成本。

  • NPTL 线程库应该与 LinuxThreads 是二进制兼容的。注意,为此我们可以使用 LD_ASSUME_KERNEL,这会在本文稍后进行讨论。

  • 这个新线程库应该可以利用 NUMA 支持的优点。

NPTL 的优点

与 LinuxThreads 相比,NPTL 具有很多优点:

  • NPTL 没有使用管理线程。管理线程的一些需求,例如向作为进程一部分的所有线程发送终止信号,是并不需要的;因为内核本身就可以实现这些功能。内核还会处理每个 线程堆栈所使用的内存的回收工作。它甚至还通过在清除父线程之前进行等待,从而实现对所有线程结束的管理,这样可以避免僵尸进程的问题。

  • 由于 NPTL 没有使用管理线程,因此其线程模型在 NUMA 和 SMP 系统上具有更好的可伸缩性和同步机制。

  • 使用 NPTL 线程库与新内核实现,就可以避免使用信号来对线程进行同步了。为了这个目的,NPTL 引入了一种名为 futex 的新机制。futex 在共享内存区域上进行工作,因此可以在进程之间进行共享,这样就可以提供进程间 POSIX 同步机制。我们也可以在进程之间共享一个 futex。这种行为使得进程间同步成为可能。实际上,NPTL 包含了一个 PTHREAD_PROCESS_SHARED 宏,使得开发人员可以让用户级进程在不同进程的线程之间共享互斥锁。

  • 由于 NPTL 是 POSIX 兼容的,因此它对信号的处理是按照每进程的原则进行的;getpid() 会为所有的线程返回相同的进程 ID。例如,如果发送了 SIGSTOP 信号,那么整个进程都会停止;使用 LinuxThreads,只有接收到这个信号的线程才会停止。这样可以在基于 NPTL 的应用程序上更好地利用调试器,例如 GDB。

  • 由于在 NPTL 中所有线程都具有一个父进程,因此对父进程汇报的资源使用情况(例如 CPU 和内存百分比)都是对整个进程进行统计的,而不是对一个线程进行统计的。

  • NPTL 线程库所引入的一个实现特性是对 ABI(应用程序二进制接口)的支持。这帮助实现了与 LinuxThreads 的向后兼容性。这个特性是通过使用 LD_ASSUME_KERNEL 实现的,下面就来介绍这个特性。

LD_ASSUME_KERNEL 环境变量

正如上面介绍的一样,ABI 的引入使得可以同时支持 NPTL 和 LinuxThreads 模型。基本上来说,这是通过 ld (一个动态链接器/加载器)来进行处理的,它会决定动态链接到哪个运行时线程库上。

举例来说,下面是 WebSphere® Application Server 对这个变量所使用的一些通用设置;您可以根据自己的需要进行适当的设置:

  • LD_ASSUME_KERNEL=2.4.19:这会覆盖 NPTL 的实现。这种实现通常都表示使用标准的 LinuxThreads 模型,并启用浮动堆栈的特性。
  • LD_ASSUME_KERNEL=2.2.5:这会覆盖 NPTL 的实现。这种实现通常都表示使用 LinuxThreads 模型,同时使用固定堆栈大小。

我们可以使用下面的命令来设置这个变量:

export LD_ASSUME_KERNEL=2.4.19

注意,对于任何 LD_ASSUME_KERNEL 设置的支持都取决于目前所支持的线程库的 ABI 版本。例如,如果线程库并不支持 2.2.5 版本的 ABI,那么用户就不能将 LD_ASSUME_KERNEL 设置为 2.2.5。通常,NPTL 需要 2.4.20,而 LinuxThreads 则需要 2.4.1。

如果您正运行的是一个启用了 NPTL 的 Linux 发行版,但是应用程序却是基于 LinuxThreads 模型来设计的,那么所有这些设置通常都可以使用。

GNU_LIBPTHREAD_VERSION 宏

大部分现代 Linux 发行版都预装了 LinuxThreads 和 NPTL,因此它们提供了一种机制来在二者之间进行切换。要查看您的系统上正在使用的是哪个线程库,请运行下面的命令:

$ getconf GNU_LIBPTHREAD_VERSION

在我的ubuntu9.04下面的输出结果:

NPTL 2.9

VerilogHDL里面的阻塞与非阻塞赋值

接触过Verilog HDL的都对阻塞与非阻塞赋值略知一二,这也是经常强调的重点之一。在编写可综合代码的时候,建议大家不要忘了打开RTL网表查看器看看自己综合出来的电路是不是想要的逻辑。

1、连续赋值

连续赋值语句的硬件实现是:从赋值语句(=)右边提取出的逻辑,用于驱动赋值语句左边的线网(net)连续赋值语句

module continousassignment(a,b,c);
input a,b;
output c;
assign c=a&b;
endmodule


综合以后,通过网表查看器为上图的结果,线网c由赋值语句的右边的逻辑是组合逻辑a&b简单驱动

2、过程赋值

过程赋值语句的硬件实现是,从赋值语句的(=或<=)右边提取出的逻辑用于驱动赋值语句左边的变量(必需是reg类 型)。必须注意的是虽然过程赋值语句是可以出现在initial语句中(仅用于仿真),也可以出现在“always”块语句中,但是只有“always” 中的过程赋值语句才能被综合。有两种类型的过程赋值语句:阻塞赋值语句(Blocking Assignment statement)、非阻塞赋值语句(non-Blocking Assignment statement)

2.1、阻塞赋值语句

阻塞赋值语句可以简单描述为,在一个always块中,语句按照从上到下的顺序执行

module blockingassignment (clk ,q1,q2);
input clk;
output [2:0] q1,q2;
reg[2:0] q1,q2;
always @ (posedge clk)
begin
q1=q1+3'b1;
q2=q1;
end
endmodule

综合后的RTL视图如上图所示,每个时钟上升沿触发后,变量q2、q1的值是同步的,q2被赋予了q1更新后的值。

2.2、非阻塞赋值语句

非阻塞赋值语句简述为:在一个always 块中,语句是并行执行的

module nonblockingassignment (clk ,q1,q2);
input clk;
output [2:0] q1,q2;
reg[2:0] q1,q2;
always @ (posedge clk)
begin
q1<=q1+3'b1; q2<=q1; end endmodule

从综合结果的RTL查看器可以看到如上图所示,每个时钟触发后,q2被赋予的q1值时上个时钟周期生成的值。从上面可以看出,第一条语句综合出来的结果是一样的,阻塞与非阻塞不同在于它们会影响到后面引用该条语句的逻辑。

3、阻塞与非阻塞建模建模原则

1)、组合逻辑使用阻塞语句、时序逻辑使用非阻塞语句;

2)、在同一个模块里,同一个变量不能既有阻塞赋值,又有非阻塞赋值。

ARM VS X86 的时代

自从订阅了以“arm+linux”为关键字的google新闻简报以来,这几年每天都可以收到几个相关的报道,我也时刻关心着技术的发展方向,毕竟身在江湖嘛!
嵌入式处理器从十年前的群雄并起到现在ARM和Intel两强争雄,波澜壮阔,演绎了一出纷繁芜杂而又让人感慨万千的大戏!
自从英特尔携凌动杀入嵌入式市场,就与在该市场获得广泛应用的ARM及相关DSP平台展开激烈的市场争夺战。
两大平台相互学习,似乎将殊途同归,应用的交叉会更多。不同的人会选择不同的方案。将来,可能是用英特尔的平台也可以,用ARM的平台也可以。但有一点很重要,硬件只不过是一个躯壳,软件才是产生价值的灵魂。项目会不会有竞争力,并不取决于你采用的是英特尔还是ARM,我认为取决于Design,即软件的价值,还有商业模式、生态环境和创新的功能。在这方面,苹果的成功就是一个例子。我们今天都是站在巨人的肩膀上前行,一个idea出来,一个创新出来了,附加值产生了,这个附加值很可能就是软件带来的。

Thursday, July 9, 2009

团队-TEAM

这个世界上没有完美的人,但是有些人热爱追求完美的事物,所以他门走到了一起。他们用他人的长处来弥补自己的短处,这个队伍称之为团队。

Tuesday, July 7, 2009

我的战术刀具

呵呵,生活就是充满了乐趣的,男人爱刀就像爱女人一样,你永远不知道哪一把是最好的,追求美好的步伐永无止境,呵呵,不过追求玩具和追求女人毕竟是不一样的!女朋友,我爱的就是最好的!
我承认我是个军刀爱好者,我喜欢购买各种战术刀具,从廉价的国产刀具到正宗进口的国外品牌刀具我都喜欢,刀子这种东西,重在实用,我买这些东西不是为了收藏,当然,工业化量产的商品收藏起来又有多大意义呢?

我的第一把刀, 国产高仿Strider.BT



买的第二把刀,SCHRADA EXTREME 野战救生刀



第三把刀,ColdSteel(冷钢)13RTK 侦察兵



卡巴1217


哈哈,我的刀具很少,我喜欢户外运动,这也是我玩刀的一个主要原因,欢迎喜欢玩刀的网友与我联系,共同交流心得,呵呵!

Friday, July 3, 2009

linux socket select笔记

有了epoll之后有人说select是应该扔到垃圾堆的东西,呵呵,我不这么认为,我是网络编程的菜鸟,实质上epoll只是select的改进而已,本质上区别不大。
select()机制提供一个fd_set数据结构,实际上是long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件可读,下面具体解释:

struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合 FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。

struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

使用select需要包含以下linux头文件:

#include /sys/types.h/
#include /sys/times.h/
#include /sys/select.h/

函数原型:
int select(
int nfds,
fd_set * readfds,
fd_set * writefds,
fd_set * exceptfds,
struct timeval * timeout);

参数解释:

ndfs:select监视的文件句柄数,视进程中打开的文件数而定,即所有文件描述符的最大值加1。
readfds:select监视的可读文件句柄集合。readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

writefds: select监视的可写文件句柄集合。writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

exceptfds:select监视的异常文件句柄集合。

timeout:本次select()的超时结束时间。(见/usr/sys/select.h,可精确至百万分之一秒)timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值下述。

返回值:
负值:select错误
正值:某些文件可读写或出错
0:等待超时,没有可读写或错误的文件

当readfds或writefds中映象的文件可读或可写或超时,本次select()就结束返回。程序员利用一组系统提供的宏在select()结束时便可判断哪一文件可读或可写。对Socket编程特别有用的就是readfds。
几只相关的宏解释如下:

FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。
FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。
FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否可读写,>0表示可读写。
(关于fd_set及相关宏的定义见/usr/include/sys/types.h)

编程框架:
int main()
{
int sock;
FILE *fp;
struct fd_set fds;
struct timeval timeout={2,0}; //select等待2秒,2秒轮询,要非阻塞就置0
char buffer[256]={0}; //256字节的接收缓冲区

/* 假定已经建立TCP连接,具体过程忽略,
*UDP同理,主机ip和port都已经给定,要写的文件已经打开
*/
sock=socket(...);
bind(...);
fp=fopen(...);

while(1)
{
FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化
FD_SET(sock,&fds); //添加描述符

FD_SET(fp,&fds); //同上
maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1

switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用
{
case -1:
exit(-1);
break; //select错误,退出程序
case 0:
break; //再次轮询
default:
if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据
{
recvfrom(sock,buffer,256,.....); //接受网络数据
if(FD_ISSET(fp,&fds)) //测试文件是否可写
fwrite(fp,buffer...); //写入文件
buffer清空;
}// end if
break;
}// end switch
}//end while
}//end main

Tuesday, June 16, 2009

动机

人们做一件事情的动机无非一下几种:
一是求名利,官场是想升官,商场无非求利,当然这个广义的名利,囊括所有的利益。
二是兴趣,像我这样,玩军用战术刀,玩飞行器,玩摄影......既花费金钱又花费时间,无非求得开心,纯属兴趣使然,我这样我高兴。
三是压力,当匪徒用刀子架到你脖子上的时候我想十有八九的人会非常无奈而又不得不输入银行卡的密码,做这样的事情是在是高压形势下的无奈之举;当然,压力也是广义的,工作压力,婚姻压力......
最后就是无聊,打发时间,那些在机场或者车子上玩游戏,看闲书的人之所以这样就是打发那段无聊的时光。
古今中外,人所成事无非上述所言!

Labels

Followers