跳至主要内容

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系统总...

【转】GPIO编程模拟I2C入门

ARM编程:ARM普通GPIO口线模拟I2C  请教个问题: 因为需要很多EEPROM进行点对点控制,所以我现在要用ARM的GPIO模拟I2C,管脚方向我设 置的是向外的。我用网上的RW24C08的万能程序修改了一下,先进行两根线的模拟,SDA6, SCL6,但是读出来的数不对。我做了一个简单的实验,模拟SDA6,SCL6输出方波,在示波 器上看到正确方波,也就是说,我的输出控制是没问题的。 哪位大哥能指点一下,是否在接收时管脚方向要设为向内?(不过IOPIN不管什么方向都可 以读出当前状态值的阿) 附修改的RW24C08()程序: #define  SomeNOP() delay(300); /**/ /* *********************************  RW24C08   **************************************** */ /**/ /* ----------------------------------------------------------------------------- ---  调用方式:void I2CInit(void)   函数说明:私有函数,I2C专用 ------------------------------------------------------------------------------- -- */ void  I2CInit( void ) ... {  IO0CLR  =  SCL6;      // 初始状态关闭总线  SomeNOP();  // 延时   I2CStop();  // 确保初始化,此时数据线是高电平 }   /**/ /* ---------------------------------------------------------------------------- ----  调用方式:void I2CSta...

【转】cs8900网卡的移植至基于linux2.6内核的s3c2410平台

cs8900网卡的移植至基于linux2.6内核的s3c2410平台(转) 2008-03-11 20:58 硬件环境:SBC-2410X开发板(CPU:S3C2410X) 内核版本:2.6.11.1 运行环境:Debian2.6.8 交叉编译环境:gcc-3.3.4-glibc-2.3.3 第一部分 网卡CS8900A驱动程序的移植 一、从网上将Linux内核源代码下载到本机上,并将其解压: #tar jxf linux-2.6.11.1.tar.bz2 二、打开内核顶层目录中的Makefile文件,这个文件中需要修改的内容包括以下两个方面。 (1)指定目标平台。 移植前:         ARCH?= $(SUBARCH) 移植后: ARCH            :=arm (2)指定交叉编译器。 移植前: CROSS_COMPILE ?= 移植后: CROSS_COMPILE   :=/opt/crosstool/arm-s3c2410-linux-gnu/gcc-3.3.4-glibc-2.3.3/bin/arm-s3c2410-linux-gnu- 注:这里假设编译器就放在本机的那个目录下。 三、添加驱动程序源代码,这涉及到以下几个方面。(1)、从网上下载了cs8900.c和cs8900.h两个针对2.6.7的内核的驱动程序源代码,将其放在drivers/net/arm/目录下面。 #cp cs8900.c ./drivers/net/arm/ #cp cs8900.h ./drivers/net/arm/ 并在cs8900_probe()函数中,memset (&priv,0,sizeof (cs8900_t));函数之后添加如下两条语句: __raw_writel(0x2211d110,S3C2410_BWSCON); __raw_writel(0x1f7c,S3C2410_BANKCON3); 注:其原因在"第二部分"解释。 (2)、修改drivers/net/arm/目录下的Kconfig文件,在最后添加如...