跳至主要内容

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); 
             } 

评论

此博客中的热门博文

【转】VxWorks中的地址映射

在运用嵌入式系统VxWorks和MPC860进行通信系统设计开发时,会遇到一个映射地址不能访问的问题。 缺省情况下,VxWorks系统已经进行了如下地址的映射:   memory地址、bcsr(Board Control and Status)地址、PC_BASE_ADRS(PCMCIA)地址、Internal Memory地址、rom(Flach memory)地址等,但是当你的硬件开发中要加上别的外设时,如(falsh、dsp、FPGA等),对这些外设的访问也是通过地址形式进行读写,如果你没有加相应的地址映射,那么是无法访问这些外设的。   和VxWorks缺省地址映射类似,你也可以进行相应的地址映射。   如下是地址映射原理及实现:   1、 地址映射结构 在Tornado\target\h\vmLib.h文件中 typedef struct phys_mem_desc { void *virtualAddr; void *physicalAddr; UINT len; UINT initialStateMask; /* mask parameter to vmStateSet */ UINT initialState; /* state parameter to vmStateSet */ } PHYS_MEM_DESC; virtualAddr:你要映射的虚拟地址 physicalAddr:硬件设计时定义的实际物理地址 len;要进行映射的地址长度 initialStateMask:可以初始化的地址状态: 有如下状态: #define VM_STATE_MASK_VALID 0x03 #define VM_STATE_MASK_WRITABLE 0x0c #define VM_STATE_MASK_CACHEABLE 0x30 #define VM_STATE_MASK_MEM_COHERENCY 0x40 #define VM_STATE_MASK_GUARDED 0x80 不同的CPU芯片类型还有其特殊状态 initialState:实际初始化的地址状态: 有如下状态: #define VM_STATE_VALID 0x01 #define VM_STATE_VALID_NOT 0x00 #define VM_STATE_WRITA

【转】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文件,在最后添加如下内容: Config ARM_CS8900    tristate "CS8900 support" depends on NET_ETHERNET && A

【转】多迷人Gtkmm啊

前边已经说过用glade设计界面然后动态装载,接下来再来看看怎么改变程序的皮肤(主题)     首先从 http://art.gnome.org/themes/gtk2 下载喜欢的主题,从压缩包里提取gtk-2.0文件夹让它和我们下边代码生成的可执行文件放在同一个目录下,这里我下载的的 http://art.gnome.org/download/themes/gtk2/1317/GTK2-CillopMidnite.tar.gz     然后用glade设计界面,命名为main.glade,一会让它和我们下边代码生成的可执行程序放在同一个目录下边     然后开始写代码如下: //main.cc #include <gtkmm.h> #include <libglademm/xml.h> int main(int argc, char *argv[]) {     Gtk::Main kit(argc,argv);         Gtk::Window *pWnd;        gtk_rc_parse("E:\\theme-viewer\\themes\\gtk-2.0\\gtkrc");       Glib::RefPtr<Gnome::Glade::Xml> refXml;     try     {         refXml = Gnome::Glade::Xml::create("main.glade");     }     catch(const Gnome::Glade::XmlError& ex)     {         Gtk::MessageDialog dialog("Load glade file failed!", false,       \                                   Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);         dialog.run();               return 1;     }         refXml->get_widget("main", pWnd);     if(pW