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

Tuesday, July 14, 2009

Linux 多线程应用中如何编写安全的信号处理函数

在开发多线程应用时,开发人员一般都会考虑线程安全,会使用 pthread_mutex 去保护全局变量。如果应用中使用了信号,而且信号的产生不是因为程序运行出错,而是程序逻辑需要,譬如 SIGUSR1、SIGRTMIN 等,信号在被处理后应用程序还将正常运行。在编写这类信号处理函数时,应用层面的开发人员却往往忽略了信号处理函数执行的上下文背景,没有考虑编写安全的 信号处理函数的一些规则。本文首先介绍编写信号处理函数时需要考虑的一些规则;然后举例说明在多线程应用中如何构建模型让因为程序逻辑需要而产生的异步信 号在指定的线程中以同步的方式处理。

线程和信号

Linux 多线程应用中,每个线程可以通过调用 pthread_sigmask() 设置本线程的信号掩码。一般情况下,被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错如 SIGSEGV;另外不能被忽略处理的信号 SIGKILL 和 SIGSTOP 也无法被阻塞。

当一个线程调用 pthread_create() 创建新的线程时,此线程的信号掩码会被新创建的线程继承。

POSIX.1 标准定义了一系列线程函数的接口,即 POSIX threads(Pthreads)。Linux C 库提供了两种关于线程的实现:LinuxThreads 和 NPTL(Native POSIX Threads Library)。LinuxThreads 已经过时,一些函数的实现不遵循POSIX.1 规范。NPTL 依赖 Linux 2.6 内核,更加遵循 POSIX..1 规范,但也不是完全遵循。

基于 NPTL 的线程库,多线程应用中的每个线程有自己独特的线程 ID,并共享同一个进程ID。应用程序可以通过调用 kill(getpid(),signo) 将信号发送到进程,如果进程中当前正在执行的线程没有阻碍此信号,则会被中断,线号处理函数会在此线程的上下文背景中执行。应用程序也可以通过调用 pthread_kill(pthread_t thread, int sig) 将信号发送给指定的线程,则线号处理函数会在此指定线程的上下文背景中执行。

基于 LinuxThreads 的线程库,多线程应用中的每个线程拥有自己独特的进程 ID,getpid() 在不同的线程中调用会返回不同的值,所以无法通过调用 kill(getpid(),signo) 将信号发送到整个进程。


编写安全的异步信号处理函数

信号的产生可以是:

  • 用户从控制终端终止程序运行,如 Ctrk + C 产生 SIGINT;
  • 程序运行出错时由硬件产生信号,如访问非法地址产生 SIGSEGV;
  • 程序运行逻辑需要,如调用 killraise 产生信号。

因为信号是异步事件,即信号处理函数执行的上下文背景是不确定的,譬如一个线程在调用某个库函数时可能会被信号中断,库函数提前出错返回,转而去执 行信号处理函数。对于上述第三种信号的产生,信号在产生、处理后,应用程序不会终止,还是会继续正常运行,在编写此类信号处理函数时尤其需要小心,以免破 坏应用程序的正常运行。关于编写安全的信号处理函数主要有以下一些规则:

  • 信号处理函数尽量只执行简单的操作,譬如只是设置一个外部变量,其它复杂的操作留在信号处理函数之外执行;
  • errno 是线程安全,即每个线程有自己的 errno,但不是异步信号安全。如果信号处理函数比较复杂,且调用了可能会改变 errno 值的库函数,必须考虑在信号处理函数开始时保存、结束的时候恢复被中断线程的 errno 值;
  • 信号处理函数只能调用可以重入的 C 库函数;譬如不能调用 malloc(),free()以及标准 I/O 库函数等;
  • 信号处理函数如果需要访问全局变量,在定义此全局变量时须将其声明为 volatile,以避免编译器不恰当的优化。

从整个 Linux 应用的角度出发,因为应用中使用了异步信号,程序中一些库函数在调用时可能被异步信号中断,此时必须根据errno 的值考虑这些库函数调用被信号中断后的出错恢复处理,譬如socket 编程中的读操作:

     rlen = recv(sock_fd, buf, len, MSG_WAITALL);
if ((rlen == -1) && (errno == EINTR)){
// this kind of error is recoverable, we can set the offset change
//‘rlen’ as 0 and continue to recv
}

在指定的线程中以同步的方式处理异步信号

如上文所述,不仅编写安全的异步信号处理函数本身有很多的规则束缚;应用中其它地方在调用可被信号中断的库函数时还需考虑被中断后的出错恢复处理。这让程序的编写变得复杂,幸运的是,POSIX.1 规范定义了sigwait()、 sigwaitinfo() pthread_sigmask() 等接口,可以实现:

这种在指定的线程中以同步方式处理信号的模型可以避免因为处理异步信号而给程序运行带来的不确定性和潜在危险。

sigwait

sigwait() 提供了一种等待信号的到来,以串行的方式从信号队列中取出信号进行处理的机制。sigwait()只等待函数参数中指定的信号集,即如果新产生的信号不在指定的信号集内,则 sigwait()继续等待。对于一个稳定可靠的程序,我们一般会有一些疑问:

  • 多个相同的信号可不可以在信号队列中排队?
  • 如果信号队列中有多个信号在等待,在信号处理时有没有优先级规则?
  • 实时信号和非实时信号在处理时有没有什么区别?

笔者写了一小段测试程序来测试 sigwait 在信号处理时的一些规则。


清单 1. sigwait_test.c
#include signal.h
#include /
errno.h
#include /
pthread.h
#include /
unistd.h
#include /
sys/types.h

void sig_handler(int signum)
{
printf("Receive signal. %d\n", signum);
}

void* sigmgr_thread()
{
sigset_t waitset, oset;
int sig;
int rc;
pthread_t ppid = pthread_self();

pthread_detach(ppid);

sigemptyset(&waitset);
sigaddset(&waitset, SIGRTMIN);
sigaddset(&waitset, SIGRTMIN+2);
sigaddset(&waitset, SIGRTMAX);
sigaddset(&waitset, SIGUSR1);
sigaddset(&waitset, SIGUSR2);

while (1) {
rc = sigwait(&waitset, &sig);
if (rc != -1) {
sig_handler(sig);
} else {
printf("sigwaitinfo() returned err: %d; %s\n", errno, strerror(errno));
}
}
}


int main()
{
sigset_t bset, oset;
int i;
pid_t pid = getpid();
pthread_t ppid;

sigemptyset(&bset);
sigaddset(&bset, SIGRTMIN);
sigaddset(&bset, SIGRTMIN+2);
sigaddset(&bset, SIGRTMAX);
sigaddset(&bset, SIGUSR1);
sigaddset(&bset, SIGUSR2);

if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)
printf("!! Set pthread mask failed\n");

kill(pid, SIGRTMAX);
kill(pid, SIGRTMAX);
kill(pid, SIGRTMIN+2);
kill(pid, SIGRTMIN);
kill(pid, SIGRTMIN+2);
kill(pid, SIGRTMIN);
kill(pid, SIGUSR2);
kill(pid, SIGUSR2);
kill(pid, SIGUSR1);
kill(pid, SIGUSR1);

// Create the dedicated thread sigmgr_thread() which will handle signals synchronously
pthread_create(&ppid, NULL, sigmgr_thread, NULL);

sleep(10);

exit (0);
}

程序编译运行在 RHEL4 的结果如下:


图 1. sigwait 测试程序执行结果
sigwait 测试程序执行结果

从以上测试程序发现以下规则:

  • 对于非实时信号,相同信号不能在信号队列中排队;对于实时信号,相同信号可以在信号队列中排队。
  • 如果信号队列中有多个实时以及非实时信号排队,实时信号并不会先于非实时信号被取出,信号数字小的会先被取出:如 SIGUSR1(10)会先于 SIGUSR2 (12),SIGRTMIN(34)会先于 SIGRTMAX (64), 非实时信号因为其信号数字小而先于实时信号被取出。

sigwaitinfo() 以及 sigtimedwait() 也提供了与 sigwait() 函数相似的功能。

Linux 多线程应用中的信号处理模型

在基于 Linux 的多线程应用中,对于因为程序逻辑需要而产生的信号,可考虑调用 sigwait()使用同步模型进行处理。其程序流程如下:

  1. 主线程设置信号掩码,阻碍希望同步处理的信号;主线程的信号掩码会被其创建的线程继承;
  2. 主线程创建信号处理线程;信号处理线程将希望同步处理的信号集设为 sigwait()的第一个参数。
  3. 主线程创建工作线程。

图 2. 在指定的线程中以同步方式处理异步信号的模型

代码示例

以下为一个完整的在指定的线程中以同步的方式处理异步信号的程序。

主线程设置信号掩码阻碍 SIGUSR1 和 SIGRTMIN 两个信号,然后创建信号处理线程sigmgr_thread()和五个工作线程 worker_thread()。主线程每隔10秒调用 kill() 对本进程发送 SIGUSR1 和 SIGTRMIN 信号。信号处理线程 sigmgr_thread()在接收到信号时会调用信号处理函数 sig_handler()

程序编译:gcc -o signal_sync signal_sync.c -lpthread

程序执行:./signal_sync

从程序执行输出结果可以看到主线程发出的所有信号都被指定的信号处理线程接收到,并以同步的方式处理。


清单 2. signal_sync.c
#include signal.h
#include /
errno.h
#include /
pthread.h
#include /
unistd.h
#include /
sys/types.h

void sig_handler(int signum)
{
static int j = 0;
static int k = 0;
pthread_t sig_ppid = pthread_self();
// used to show which thread the signal is handled in.

if (signum == SIGUSR1) {
printf("thread %d, receive SIGUSR1 No. %d\n", sig_ppid, j);
j++;
//SIGRTMIN should not be considered constants from userland,
//there is compile error when use switch case
} else if (signum == SIGRTMIN) {
printf("thread %d, receive SIGRTMIN No. %d\n", sig_ppid, k);
k++;
}
}

void* worker_thread()
{
pthread_t ppid = pthread_self();
pthread_detach(ppid);
while (1) {
printf("I'm thread %d, I'm alive\n", ppid);
sleep(10);
}
}

void* sigmgr_thread()
{
sigset_t waitset, oset;
siginfo_t info;
int rc;
pthread_t ppid = pthread_self();

pthread_detach(ppid);

sigemptyset(&waitset);
sigaddset(&waitset, SIGRTMIN);
sigaddset(&waitset, SIGUSR1);

while (1) {
rc = sigwaitinfo(&waitset, &info);
if (rc != -1) {
printf("sigwaitinfo() fetch the signal - %d\n", rc);
sig_handler(info.si_signo);
} else {
printf("sigwaitinfo() returned err: %d; %s\n", errno, strerror(errno));
}
}
}


int main()
{
sigset_t bset, oset;
int i;
pid_t pid = getpid();
pthread_t ppid;


// Block SIGRTMIN and SIGUSR1 which will be handled in
//dedicated thread sigmgr_thread()
// Newly created threads will inherit the pthread mask from its creator
sigemptyset(&bset);
sigaddset(&bset, SIGRTMIN);
sigaddset(&bset, SIGUSR1);
if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)
printf("!! Set pthread mask failed\n");

// Create the dedicated thread sigmgr_thread() which will handle
// SIGUSR1 and SIGRTMIN synchronously
pthread_create(&ppid, NULL, sigmgr_thread, NULL);

// Create 5 worker threads, which will inherit the thread mask of
// the creator main thread
for (i = 0; i < i =" 0;">

注意事项

在基于 Linux 的多线程应用中,对于因为程序逻辑需要而产生的信号,可考虑使用同步模型进行处理;而对会导致程序运行终止的信号如 SIGSEGV 等,必须按照传统的异步方式使用 signal()sigaction()注册信号处理函数进行处理。这两种信号处理模型可根据所处理的信号的不同同时存在一个 Linux 应用中:

  • 不要在线程的信号掩码中阻塞不能被忽略处理的两个信号 SIGSTOP 和 SIGKILL。
  • 不要在线程的信号掩码中阻塞 SIGFPE、SIGILL、SIGSEGV、SIGBUS。
  • 确保 sigwait() 等待的信号集已经被进程中所有的线程阻塞。
  • 在主线程或其它工作线程产生信号时,必须调用 kill() 将信号发给整个进程,而不能使用 pthread_kill() 发送某个特定的工作线程,否则信号处理线程无法接收到此信号。
  • 因为 sigwait()使用了串行的方式处理信号的到来,为避免信号的处理存在滞后,或是非实时信号被丢失的情况,处理每个信号的代码应尽量简洁、快速,避免调用会产生阻塞的库函数。

小结

在开发 Linux 多线程应用中, 如果因为程序逻辑需要引入信号, 在信号处理后程序仍将继续正常运行。在这种背景下,如果以异步方式处理信号,在编写信号处理函数一定要考虑异步信号处理函数的安全; 同时, 程序中一些库函数可能会被信号中断,错误返回,这时需要考虑对 EINTR 的处理。另一方面,也可考虑使用上文介绍的同步模型处理信号,简化信号处理函数的编写,避免因为信号处理函数执行上下文的不确定性而带来的风险。

come from:http://www.ibm.com/developerworks/cn/linux/l-cn-signalsec/

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

Labels

Followers