跳至主要内容

Linux程序设计入门 - fork, pthread, and signals

在UNIX程序设计中,学会fork及signal的运用,算是相当基本的功夫。 

fork()及signal经常运用在daemon守护神这一类常驻程序,另外像 a4c.tty/yact/chdrv这些中文终端机程序也会用到,一般如 Mozilla/Apache/Squid等大程序几乎都会用到。 

虽 然在UNIX下的程序写作,对thread的功能需求并非很大,但thread在现代的 作业系统中,几乎都已经存在了。pthread是Linux上的thread函数库,如果您 要在Linux下撰写多线程程序,例如MP3播放器,熟悉pthread的用法是必要的。 

pthread及signal都可以用一大 章来讨论。在这里,我只谈及最简单及常用的技巧,当您熟悉这些基本技巧的运用后,再找一些专门深入探讨pthread及signal 程序写作的书籍来研究。这些进阶的写法,用到的机会较少,将层次分明,学习速度应该会比较快。 

程序分支fork()

fork()会产生一个与父程序相同的子程序,唯一不同之处在于其process id(pid)。

如果我们要撰写守护神程序,或是例如网络伺服器,需要多个进程来同时提供多个连线,可以利用fork()来产生多个相同的进程。

函数声明

pid_t fork(void); 
pid_t vfork(void);

返回值:

-1 : 失败。 
  0 : 子程序。 
>0 : 将子程序的process id传回给父程序。

在Linux下fork()及vfork()是相同的东西。

范例一: fork.c

在这个范例中,我们示范fork()的标准用法。

#include 
#include 
#include

void main(void) 

  pid_t pid;

  printf("hello\n"); 
  pid = fork();

  switch (pid) { 
    case -1: printf("failure!\n"); break; 
    case  0: printf("I am child!\n"); break; 
    default: printf("my child is %d\n",pid); break; 
  } 
  for (;;) { /* do something here */ } 
}

编译:

gcc -o ex1 fork.c

执行结果:

./ex1 &

hello 
my child is 8650 
I am child!

我们可以见到,使用fork(),可将一个程式分成两个。在分之前的程序代码只执行一次。

检验行程:

ps | grep ex1

 8649  p0 R    0:40 ./ex1 
 8650  p0 R    0:40 ./ex1

8649是父程序的pid,8650则为子程序的pid。 
您会需要用到"killall ex1"来杀掉两个行程。

范例二: daemon.c

在UNIX中,我们一般都利用fork(),来实作所谓的"守护神程序",也就是DOS中所谓的"常驻程序"。一般的技巧是将父程序结束,而子程序便成为"守护神"。

这个范例中,示范一般标准的daemon写法。

#include 
#include 
#include

void main(void) 

  pid_t pid;

  pid = fork();

  if (pid>0) { 
    printf("daemon on duty!\n"); 
    exit(0); 
  } 

  else if (pid<0) { 
    printf("Can't fork!\n"); 
    exit(-1); 
  }

  for (;;) { 
    printf("I am the daemon!\n"); 
    sleep(3); 
    /* do something your own here */ 
  }

}

编译:

gcc -o ex2 daemon.c

执行结果:

./ex2

daemon on duty! 
I am the daemon! 
接下来每三秒钟,都会出现一个"I am the daemon!"的信息,这表示您的程序已经"长驻"在系统中了。

检验进程:

ps | grep ex2

8753  p0 S    0:00 ./ex2

注意到在范例一中,我们下的指令为"./ex1 &",而在范例二中为"./ex2",没有"&"符号。 
 

范例三: lock.c

许多的时候,我们希望"守护神"在系统中只有一个,这时候会需要用到pid lock的技巧。如果您注意到/var/run目录中的内容,您会发现到有许多的*.pid档,观看其内容都是一些数字,这些数字其实就是该行程的pid。

#include 
#include 
#include

void main(void) 

  FILE *fp; 
  pid_t pid;

  if (access("/var/run/lock.pid",R_OK)==0) { 
    printf("Existing a copy of this daemon!\n"); 
    exit(1); 
  }

  pid = fork();

  if (pid>0) { 
    printf("daemon on duty!\n");

    fp = fopen("/var/run/lock.pid","wt"); 
    fprintf(fp,"%d",pid); 
    fclose(fp);

    exit(0); 
  } 

  else if (pid<0) { 
    printf("Can't fork!\n"); 
    exit(-1); 
  } 
  
   for (;;) { 
    printf("I am the daemon!\n"); 
    sleep(3); 
  } 
}

编译:

gcc -o ex3 lock.c

执行:

./ex3

daemon on duty! 
I am the daemon!

再执行一次 
./ex3

Existing a copy of this daemon!

这时如果您将该行程杀掉,并重新执行: 
killall ex3 
./ex3

Existing a copy of this daemon!

您 会发现daemon无法再度长驻,因为/var/run/lock.pid并没有因为进程被杀掉而删除掉。一般来说,开机后的启动Script,会将 /var/run中所有内容自动清除,以避免这个问题的发生。如果您想要在进程被杀掉时,将/var/run/lock.pid也一并删除,那么您需要利 用signal来处理这件事。

您可手动删除该档案,daemon便可再度执行。 
rm /var/run/lock.pid

范例四: children.c

如果您正在写伺服器,您可能会需要复制出许多的子进程,用以提供同时多人的服务,这时可利用fork(),一次复制出多个子进程。最佳的例子为Apache WWW Server。

#include 
#include 
#include

#define MAX_CHILD 9

void main(void) 

  pid_t pid; 
  int  n;

  printf("hello\n");

  n = 0; 
  do {

    pid = fork(); 
    n++;

    switch (pid) { 
      case -1: 
        printf("failure!\n"); 
        exit(-1); 
      break; 
      case  0: 
        printf("I am child %d!\n",n); 
      break; 
      default: 
        printf("my child is %d\n",pid); break; 
    } 
  } while (pid!=0&&nMAX_CHILD

  if (pid>0) exit(0);

  for (;;) { /* do something here */ } 
}

编译:

gcc -o ex4 children.c

执行结果:

./ex4

hello 
my child is 8863 
I am child 1! 
my child is 8864 
I am child 2! 
my child is 8865 
I am child 3! 
my child is 8866 
I am child 4! 
my child is 8867 
I am child 5! 
my child is 8868 
I am child 6! 
my child is 8869 
I am child 7! 
my child is 8870 
I am child 8! 
my child is 8871 
I am child 9!

检验进程:

ps | grep ex4

 8863  p0 R    0:12 ./ex4 
 8864  p0 R    0:12 ./ex4 
 8865  p0 R    0:12 ./ex4 
 8866  p0 R    0:12 ./ex4 
 8867  p0 R    0:12 ./ex4 
 8868  p0 R    0:12 ./ex4 
 8869  p0 R    0:11 ./ex4 
 8870  p0 R    0:12 ./ex4 
 8871  p0 R    0:12 ./ex4 
 


thread

我假设您对thread已经有一些基本的概念,因此,在此我将著重於如何实作。

函数声明

  • int  pthread_create(pthread_t  *  thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg);
  • int pthread_join(pthread_t th, void **thread_return);
  • int pthread_detach(pthread_t th);
  • void pthread_exit(void *retval);
  • int pthread_attr_init(pthread_attr_t *attr);

资料结构

typedef struct 

  int detachstate; 
  int schedpolicy; 
  struct sched_param schedparam; 
  int inheritsched; 
  int scope; 
} pthread_attr_t;

范例一:

#include 
#include 
#include 
#include

void * mythread(void *arg) 
{

  for (;;) { 
    printf("thread\n"); 
    sleep(1); 
  } 
  return NULL; 
}

void main(void) 

  pthread_t th;

  if (pthread_create(&th,NULL,mythread,NULL)!=0) exit(0);

  for (;;) { 
    printf("main process\n"); 
    sleep(3); 
  } 
}

执行结果:

./ex1

main process 
thread 
thread 
thread 
main process 
thread 
thread 
thread 
main process 
thread 
thread 
thread 
main process


信号singals

信 号的处理可以用一大章来写,涉及的层面也会深入整个作业系统中,我并不打算这样做,因为您可能会越搞越迷糊。这里我只告诉您如何接上信号,在实用的层面 上,这样便很够用了。您可以先利用这些基本的技巧来编写程序,等到有进一步高级应用的需要时,找一本较深入的UNIX Programming教材,专门研究signal的写法。

一般简单的signal写法如下:

void mysignal(int signo) 

  /* my signal handler */ 
}

void initsignal(void) 

  struct sigaction act;

  act.sa_handler = mysignal; 
  act.sa_flags   = 0; 
  sigemptyset(&act.sa_mask); 
  sigaction(SIGHUP,&act,NULL); 
  sigaction(SIGINT,&act,NULL); 
  sigaction(SIGQUIT,&act,NULL); 
  sigaction(SIGILL,&act,NULL); 
  sigaction(SIGTERM,&act,NULL); 

 

范例一: lock.c

在fork的范例三中提到,在daemon被杀掉时,需要在离开前,将/var/run/lock.pid删除。这里我们可以利用signal来处理这件事。

#include 
#include 
#include 
#include

#define LOCK_FILE "/var/run/lock.pid"

void quit(int signo) 

  printf("Receive signal %d\n",signo); 
  unlink(LOCK_FILE); 
  exit(1); 

  
void main(void) 

  FILE *fp; 
  pid_t pid; 
  struct sigaction act;

  if (access(LOCK_FILE,R_OK)==0) { 
    printf("Existing a copy of this daemon!\n"); 
    exit(1); 
  }

  pid = fork();

  if (pid>0) { 
    printf("daemon on duty!\n");

    fp = fopen(LOCK_FILE,"wt"); 
    fprintf(fp,"%d",pid); 
    fclose(fp); 
  } else 
    exit(0);  if (pid<0) { 
    printf("Can't fork!\n"); 
    exit(-1); 
  }

  act.sa_handler = quit; 
  act.sa_flags   = 0; 
  sigemptyset(&act.sa_mask); 
  sigaction(SIGTERM,&act,NULL); 
  sigaction(SIGHUP,&act,NULL); 
  sigaction(SIGINT,&act,NULL); 
  sigaction(SIGQUIT,&act,NULL); 
  sigaction(SIGUSR1,&act,NULL); 
  sigaction(SIGUSR2,&act,NULL);

  for (;;) { 
    sleep(3); 
  } 
}

编译:

gcc -o ex1 lock.c

执行

./ex1

daemon on duty!

送信号

我们先找出该守护神程序的pid

PID=`cat /var/run/lock.pid`

接下来利用kill来送信号

kill $PID

Receive signal 15

程序将会结束,并且/var/run/lock.pid将会被删除掉,以便下一次daemon再启动。注意到如果quit函数内,没有放exit(),程序将永远杀不掉。

接下来送一些其它的信号试试看。 
./ex1 
PID=`cat /var/run/lock.pid` 
kill -HUP $PID

Receive signal 1

您可以自行试试 
kill -INT $PID 
kill -QUIT $PID 
kill -ILL $PID 



等等这些信号,看看他们的结果如何。

信号的定义

在/usr/include/signum.h中有各种信号的定义 
#define SIGHUP          1       /* Hangup (POSIX).  */ 
#define SIGINT          2       /* Interrupt (ANSI).  */ 
#define SIGQUIT         3       /* Quit (POSIX).  */ 
#define SIGILL          4       /* Illegal instruction (ANSI).  */ 
#define SIGTRAP         5       /* Trace trap (POSIX).  */ 
#define SIGABRT         6       /* Abort (ANSI).  */ 
#define SIGIOT          6       /* IOT trap (4.2 BSD).  */ 
#define SIGBUS          7       /* BUS error (4.2 BSD).  */ 
#define SIGFPE          8       /* Floating-point exception (ANSI).  */ 
#define SIGKILL         9       /* Kill, unblockable (POSIX).  */ 
#define SIGUSR1         10      /* User-defined signal 1 (POSIX).  */ 
#define SIGSEGV         11      /* Segmentation violation (ANSI).  */ 
#define SIGUSR2         12      /* User-defined signal 2 (POSIX).  */ 
#define SIGPIPE         13      /* Broken pipe (POSIX).  */ 
#define SIGALRM         14      /* Alarm clock (POSIX).  */ 
#define SIGTERM         15      /* Termination (ANSI).  */ 
#define SIGSTKFLT       16      /* ??? */ 
#define SIGCLD          SIGCHLD /* Same as SIGCHLD (System V).  */ 
#define SIGCHLD         17      /* Child status has changed (POSIX).  */ 
#define SIGCONT         18      /* Continue (POSIX).  */ 
#define SIGSTOP         19      /* Stop, unblockable (POSIX).  */ 
#define SIGTSTP         20      /* Keyboard stop (POSIX).  */ 
#define SIGTTIN         21      /* Background read from tty (POSIX).  */ 
#define SIGTTOU         22      /* Background write to tty (POSIX).  */ 
#define SIGURG          23      /* Urgent condition on socket (4.2 BSD).  */ 
#define SIGXCPU         24      /* CPU limit exceeded (4.2 BSD).  */ 
#define SIGXFSZ         25      /* File size limit exceeded (4.2 BSD).  */ 
#define SIGVTALRM       26      /* Virtual alarm clock (4.2 BSD).  */ 
#define SIGPROF         27      /* Profiling alarm clock (4.2 BSD).  */ 
#define SIGWINCH        28      /* Window size change (4.3 BSD, Sun).  */ 
#define SIGPOLL         SIGIO   /* Pollable event occurred (System V).  */ 
#define SIGIO           29      /* I/O now possible (4.2 BSD).  */ 
#define SIGPWR          30      /* Power failure restart (System V).  */ 
#define SIGUNUSED       31

函数声明:

Signal Operators
  • int sigemptyset(sigset_t *set);
  • int sigfillset(sigset_t *set);
  • int sigaddset(sigset_t *set, int signum);
  • int sigdelset(sigset_t *set, int signum);
  • int sigismember(const sigset_t *set, int signum);
Signal Handling Functions
  • int sigaction(int signum,  const  struct  sigaction  *act,struct sigaction *oldact);
  • int  sigprocmask(int  how,  const  sigset_t *set, sigset_t *oldset);
  • int sigpending(sigset_t *set);
  • int sigsuspend(const sigset_t *mask);
Structure Signal Action 
struct sigaction { 
                 void (*sa_handler)(int); 
                 sigset_t sa_mask; 
                 int sa_flags; 
                 void (*sa_restorer)(void); 
             } 

评论

此博客中的热门博文

【转】AMBA、AHB、APB总线简介

AMBA 简介 随着深亚微米工艺技术日益成熟,集成电路芯片的规模越来越大。数字IC从基于时序驱动的设计方法,发展到基于IP复用的设计方法,并在SOC设计中得到了广泛应用。在基于IP复用的SoC设计中,片上总线设计是最关键的问题。为此,业界出现了很多片上总线标准。其中,由ARM公司推出的AMBA片上总线受到了广大IP开发商和SoC系统集成者的青睐,已成为一种流行的工业标准片上结构。AMBA规范主要包括了AHB(Advanced High performance Bus)系统总线和APB(Advanced Peripheral Bus)外围总线。   AMBA 片上总线        AMBA 2.0 规范包括四个部分:AHB、ASB、APB和Test Methodology。AHB的相互连接采用了传统的带有主模块和从模块的共享总线,接口与互连功能分离,这对芯片上模块之间的互连具有重要意义。AMBA已不仅是一种总线,更是一种带有接口模块的互连体系。下面将简要介绍比较重要的AHB和APB总线。 基于 AMBA 的片上系统        一个典型的基于AMBA总线的系统框图如图3所示。        大多数挂在总线上的模块(包括处理器)只是单一属性的功能模块:主模块或者从模块。主模块是向从模块发出读写操作的模块,如CPU,DSP等;从模块是接受命令并做出反应的模块,如片上的RAM,AHB/APB 桥等。另外,还有一些模块同时具有两种属性,例如直接存储器存取(DMA)在被编程时是从模块,但在系统读传输数据时必须是主模块。如果总线上存在多个主模块,就需要仲裁器来决定如何控制各种主模块对总线的访问。虽然仲裁规范是AMBA总线规范中的一部分,但具体使用的算法由RTL设计工程师决定,其中两个最常用的算法是固定优先级算法和循环制算法。AHB总线上最多可以有16个主模块和任意多个从模块,如果主模块数目大于16,则需再加一层结构(具体参阅ARM公司推出的Multi-layer AHB规范)。APB 桥既是APB总线上唯一的主模块,也是AHB系统总线上的从模块。其主要功能是锁存来自AHB系统总...

【转】C++/CLI程序进程之间的通讯

 现在,把大型软件项目分解为一些相交互的小程序似乎变得越来越普遍,程序各部分之间的通讯可使用某种类型的通讯协议,这些程序可能运行在不同的机器上、不同的操作系统中、以不同的语言编写,但也有可能只在同一台机器上,实际上,这些程序可看成是同一程序中的不同线程。而本文主要讨论C++/CLI程序间的通讯,当然,在此是讨论进程间通讯,而不是网络通讯。    简介   试想一个包含数据库查询功能的应用,通常有一个被称为服务端的程序,等待另一个被称为客户端程序发送请求,当接收到请求时,服务端执行相应功能,并把结果(或者错误信息)返回给客户端。在许多情况中,有着多个客户端,所有的请求都会在同一时间发送到同一服务端,这就要求服务端程序要更加高级、完善。   在某些针对此任务的环境中,服务端程序可能只是众多程序中的一个程序,其他可能也是服务端或者客户端程序,实际上,如果我们的数据库服务端需要访问不存在于本机的文件,那么它就可能成为其他某个文件服务器的一个客户端。一个程序中可能会有一个服务线程及一个或多个客户线程,因此,我们需小心使用客户端及服务端这个术语,虽然它们表达了近似的抽象含义,但在具体实现上却大不相同。从一般的观点来看,客户端即为服务端所提供服务的"消费者",而服务端也能成为其他某些服务的客户端。    服务端套接字   让我们从一个具体有代表性的服务端程序开始(请看例1),此程序等待客户端发送一对整数,把它们相加之后返回结果给客户端。   例1: using namespace System; using namespace System::IO; using namespace System::Net; using namespace System::Net::Sockets; int main(array<String^>^ argv) { if (argv->Length != 1) { Console::WriteLine("Usage: Server port"); Environment::Exit(1); } int port = 0; try { port = Int32::Parse(argv[0]); } catch (FormatException^ e) { Console::Wri...

【转】VxWorks入门

1.VxWorks开发方式:交叉开发,即将开发分为主机(host)和目标机(target)两部分。 类似于dos下C语言程序的开发。 合并开发的优点:简单 缺点:资源消耗量大,CPU支持,非标准体系的支持 host (Tornado) target(vxWork) 小程序模块 vxWorks实际采用开发模式 Tornado提供:编辑,编译,调试,性能分析工具,是vxWorks的开发工具 vxWorks:面向对象可以剪裁的实际运行操作系统 2.vxWorks启动方式 <1>Rom方式 (vxWork_rom) vxWorks直接烧入rom <2>Rom引导方式(bootrom+vxWorks) 其中bootrom烧入rom,vxWorks可以通过从串口,网口,硬盘,flash等下载!这里的bootrom不是开发环境中的bootable,在开发环境里bootable指的是vxWorks,downloadable指application 3.调试 <1>attachs/20060907_164540_564.rar 用来在多任务调试时将调试对象绑定到某个任务 <2>任务级调试(attachs/20060907_164540_564.rar taskName) 单个任务的调试不会影响到其他任务的运行,主要用来调用户的应用程序。 全局断点:在调另一任务或本任务时,系统运行本任务断点,则停下。各任务要配合使用。 任务断点:调本任务时,系统运行到本任务断点,则停下。如果没有attachs/20060907_164540_564.rar到本任务,不起作用。 一次性断点:跑到一次之后自动删除。 <3>系统级调试(attachs/20060907_164540_564.rar system) 把所有task和系统core、中断看成一个整体,可用于调试系统和中断。对中断调试,如果不是系统级调试,无论是那种断点都不起作用 !wdbAgent不在调试范围内,当任务级调试时工作在中断方式,系统级调试工作在轮询方式。 !可是使用命令行方式的调试,参看crossWind教程。 4.调度 优先级调度(无条件) 时间片:同优先级,如果时间片没有打开,任务采取先到先运行,运行完毕在交出cpu,如果打开,则轮流使用cpu。 !死循环使比它...