2004年10月20日

   Line disciplines
     A terminal file is used like any other file in the system in that it can
     be opened, read, and written to using standard system calls.  For each
     existing terminal file, there is a software processing module called a
     line discipline is associated with it.  The line discipline essentially
     glues the low level device driver code with the high level generic inter-
     face routines (such as read(2) and write(2)), and is responsible for
     implementing the semantics associated with the device.  When a terminal
     file is first opened by a program, the default line discipline called the
     termios line discipline is associated with the file.  This is the primary
     line discipline that is used in most cases and provides the semantics
     that users normally associate with a terminal.  When the termios line
     discipline is in effect, the terminal file behaves and is operated
     according to the rules described in termios(4).  Please refer to that man
     page for a full description of the terminal semantics.  The operations
     described here generally represent features common across all line
     disciplines, however some of these calls may not make sense in conjunc-
     tion with a line discipline other than termios, and some may not be sup-
     ported by the underlying hardware (or lack thereof, as in the case of
     ptys).

/* Initialize UART driver */
int init_uart(char *dev, struct uart_t *u, int send_break)
{
 struct termios ti;
 int  fd, i;

 fd = open(dev, O_RDWR | O_NOCTTY);//打开串口设备,其中标志

//O_RDWR,可以对此设备进行读写操作;

//O_NOCTTY,我不是很理解?

//但是不要以控制 tty 的模式,因为我们并不希望在发送 Ctrl-C
 后结束此进程

 if (fd < 0) {
  perror(“Can’t open serial port”);
  return -1;
 }

 //drop fd’s data;
 tcflush(fd, TCIOFLUSH
);//清空数据线

 if (tcgetattr(fd, &ti) < 0) {
  perror(“Can’t get port settings”);
  return -1;
 }

 cfmakeraw(&ti);

cfmakeraw sets the terminal attributes as follows://此函数设置串口终端的以下这些属性,
termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
|INLCR|IGNCR|ICRNL|IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
termios_p->c_cflag &= ~(CSIZE|PARENB) ;
termios_p->c_cflag |=CS8;

 ti.c_cflag |= CLOCAL;//本地连接,无调制解调器控制
 if (u->flags & FLOW_CTL)
  ti.c_cflag |= CRTSCTS
;//输出硬件流控(只能在具完整线路的缆线下工作,参

//考 Serial-HOWTO 第七节)
 else
  ti.c_cflag &= ~CRTSCTS;

 if (tcsetattr(fd, TCSANOW, &ti) < 0) {//启动新的串口设置
  perror(“Can’t set port settings”);
  return -1;
 }

 /* Set initial baudrate */
 if (set_speed(fd, &ti, u->init_speed) < 0) {/
/设置串口的传输速率bps, 也可以使

//用 cfsetispeed 和 cfsetospeed 来设置

  perror(“Can’t set initial baud rate”);
  return -1;
 }

 tcflush(fd, TCIOFLUSH);//清空数据线

 if (send_break)
  tcsendbreak(fd, 0);

//int tcsendbreak ( int fd, int duration );Sends a break for
//the given time.在串口线上发送0值,至少维持0.25秒。

//If duration is 0, it transmits zero-valued bits for at least 0.25 seconds, and

//not more than 0.5seconds.

 //where place register u’s init function;
 if (u->init && u->init(fd, u, &ti) < 0)

//所有bluez支持的蓝牙串口设备类型构成了一个uart结构数组,通过

//查找对应的uart类型,这个uart的init成员显示了它的init调用方法;

struct uart_t uart[] = {
 { “any”,      0×0000, 0×0000, HCI_UART_H4,   115200, 115200, FLOW_CTL, NULL },
 { “ericsson”, 0×0000, 0×0000, HCI_UART_H4,   57600,  115200, FLOW_CTL, ericsson },
 { “digi”,     0×0000, 0×0000, HCI_UART_H4,   9600,   115200, FLOW_CTL, digi },
 { “texas”,    0×0000, 0×0000, HCI_UART_H4,   115200, 115200, FLOW_CTL, texas},

 { “bcsp”,     0×0000, 0×0000, HCI_UART_BCSP, 115200, 115200, 0,        bcsp },//bcsp的init函数名为bcsp,定义在本文件中**;

  return -1;

 tcflush(fd, TCIOFLUSH);//清空数据线

 /* Set actual baudrate */
 if (set_speed(fd, &ti, u->speed) < 0) {
  perror(“Can’t set baud rate”);
  return -1;
 }

 /* Set TTY to N_HCI line discipline */
 i = N_HCI;
 if (ioctl(fd, TIOCSETD, &i) < 0) {//

TIOCSETD int *ldisc//改变到 i 行规,即hci行规
Change to the new line discipline pointed to by ldisc. The available line disciplines are listed in 

/* ioctl (fd, TIOCSERGETLSR, &result) where result may be as below */

/* line disciplines */
#define N_TTY  0

……

#define N_HCI  15  /* Bluetooth HCI UART */

  perror(“Can’t set line discipline”);
  return -1;
 }

 if (ioctl(fd, HCIUARTSETPROTO, u->proto) < 0) {

//设置hci设备的proto操作函数集为hci_uart操作集;
  perror(“Can’t set device”);
  return -1;
 }

 return fd;
}

Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍。


Linux下串口编程入门


Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍。

串口简介
串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是 RS-232-C 接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、 调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是”数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”该标准规定采用一个 25 个脚的 DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于 4% 的情况下,传输电缆长度应为 50 英尺。

Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍,如果要非常深入了解,建议看看本文所参考的《Serial Programming Guide for POSIX Operating Systems》

计算机串口的引脚说明

<><>   
序号 信号名称 符号 流向 功能
2 发送数据 TXD DTE→DCE DTE发送串行数据
3 接收数据 RXD DTE←DCE DTE 接收串行数据
4 请求发送 RTS DTE→DCE DTE 请求 DCE 将线路切换到发送方式
5 允许发送 CTS DTE←DCE DCE 告诉 DTE 线路已接通可以发送数据
6 数据设备准备好 DSR DTE←DCE DCE 准备好
7 信号地         信号公共地
8 载波检测 DCD DTE←DCE 表示 DCE 接收到远程载波
20 数据终端准备好 DTR DTE→DCE DTE 准备好
22 振铃指示 RI DTE←DCE 表示 DCE 与线路接通,出现振铃

串口操作
串口操作需要的头文件

 #include  /*标准输入输出定义*/ #include  /*标准函数库定义*/ #include  /*Unix 标准函数定义*/ #include  #include  #include  /*文件控制定义*/ #include  /*PPSIX 终端控制定义*/ #include  /*错误号定义*/ 

打开串口
在 Linux 下串口文件是位于 /dev 下的

串口一 为 /dev/ttyS0

串口二 为 /dev/ttyS1

打开串口是通过使用标准的文件打开函数操作:

 int fd; /*以读写方式打开串口*/ fd = open( "/dev/ttyS0", O_RDWR); if (-1 == fd){ /* 不能打开串口一*/ perror(" 提示错误!"); } 

设置串口
最基本的设置串口包括波特率设置,效验位和停止位设置。

串口的设置主要是设置 struct termios 结构体的各成员值。

 struct termio { unsigned short c_iflag; /* 输入模式标志 */ unsigned short c_oflag; /* 输出模式标志 */ unsigned short c_cflag; /* 控制模式标志*/ unsigned short c_lflag; /* local mode flags */ unsigned char c_line; /* line discipline */ unsigned char c_cc[NCC]; /* control characters */ }; 

设置这个结构体很复杂,我这里就只说说常见的一些设置:

波特率设置

下面是修改波特率的代码:

 struct termios Opt; tcgetattr(fd, &Opt); cfsetispeed(&Opt,B19200); /*设置为19200Bps*/ cfsetospeed(&Opt,B19200); tcsetattr(fd,TCANOW,&Opt); 

设置波特率的例子函数:

 /** *@brief 设置串口通信速率 *@param fd 类型 int 打开串口的文件句柄 *@param speed 类型 int 串口速度 *@return void */ int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300, B38400, B19200, B9600, B4800, B2400, B1200, B300, }; int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400, 1200, 300, }; void set_speed(int fd, int speed){ int i; int status; struct termios Opt; tcgetattr(fd, &Opt); for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) { if (speed == name_arr[i]) { tcflush(fd, TCIOFLUSH); cfsetispeed(&Opt, speed_arr[i]); cfsetospeed(&Opt, speed_arr[i]); status = tcsetattr(fd1, TCSANOW, &Opt); if (status != 0) { perror("tcsetattr fd1"); return; } tcflush(fd,TCIOFLUSH); } } } 

效验位和停止位的设置:

无效验 8位 Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS8;
奇效验(Odd) 7位 Option.c_cflag |= ~PARENB;
Option.c_cflag &= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS7;
偶效验(Even) 7位 Option.c_cflag &= ~PARENB;
Option.c_cflag |= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS7;
Space效验 7位 Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= &~CSIZE;
Option.c_cflag |= CS8;

设置效验的函数:

 /** *@brief 设置串口数据位,停止位和效验位 *@param fd 类型 int 打开的串口文件句柄 *@param databits 类型 int 数据位 取值 为 7 或者8 *@param stopbits 类型 int 停止位 取值为 1 或者2 *@param parity 类型 int 效验类型 取值为N,E,O,,S */ int set_Parity(int fd,int databits,int stopbits,int parity) { struct termios options; if ( tcgetattr( fd,&options) != 0) { perror("SetupSerial 1"); return(FALSE); } options.c_cflag &= ~CSIZE; switch (databits) /*设置数据位数*/ { case 7: options.c_cflag |= CS7; break; case 8: options.c_cflag |= CS8; break; default: fprintf(stderr,"Unsupported data size "); return (FALSE); } switch (parity) { case 'n': case 'N': options.c_cflag &= ~PARENB; /* Clear parity enable */ options.c_iflag &= ~INPCK; /* Enable parity checking */ break; case 'o': case 'O': options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/ options.c_iflag |= INPCK; /* Disnable parity checking */ break; case 'e': case 'E': options.c_cflag |= PARENB; /* Enable parity */ options.c_cflag &= ~PARODD; /* 转换为偶效验*/ options.c_iflag |= INPCK; /* Disnable parity checking */ break; case 'S': case 's': /*as no parity*/ options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB;break; default: fprintf(stderr,"Unsupported parity "); return (FALSE); } /* 设置停止位*/ switch (stopbits) { case 1: options.c_cflag &= ~CSTOPB; break; case 2: options.c_cflag |= CSTOPB; break; default: fprintf(stderr,"Unsupported stop bits "); return (FALSE); } /* Set input parity option */ if (parity != 'n') options.c_iflag |= INPCK; tcflush(fd,TCIFLUSH); options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/ options.c_cc[VMIN] = 0; /* Update the options and do it NOW */ if (tcsetattr(fd,TCSANOW,&options) != 0) { perror("SetupSerial 3"); return (FALSE); } return (TRUE); } 

需要注意的是:

如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯,设置方式如下:

 options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/ options.c_oflag &= ~OPOST; /*Output*/ 

读写串口
设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。

  • 发送数据
     char buffer[1024];int Length;int nByte;nByte = write(fd, buffer ,Length) 
  • 读取串口数据

    使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。

    可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作。

     char buff[1024];int Len;int readByte = read(fd,buff,Len); 

关闭串口
关闭串口就是关闭文件。

 close(fd); 

例子
下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件

 /**********************************************************************代码说明:使用串口二测试的,发送的数据是字符, 但是没有发送字符串结束符号,所以接收到后,后面加上了结束符号。我测试使用的是单片机发送数据到第二个串口,测试通过。 **********************************************************************/ #define FALSE -1 #define TRUE 0 /*********************************************************************/ int OpenDev(char *Dev) { int fd = open( Dev, O_RDWR ); //| O_NOCTTY | O_NDELAY if (-1 == fd) { perror("Can't Open Serial Port"); return -1; } else return fd; } int main(int argc, char **argv){ int fd; int nread; char buff[512]; char *dev = "/dev/ttyS1"; //串口二 fd = OpenDev(dev); set_speed(fd,19200); if (set_Parity(fd,8,1,'N') == FALSE) { printf("Set Parity Error "); exit (0); } while (1) //循环读取数据 { while((nread = read(fd, buff, 512))>0) { printf(" Len %d ",nread); buff[nread+1] = ''; printf( " %s", buff); } } //close(fd); // exit (0); } 

参考资料

2004年10月19日

hciattach.c->main.c中:

 /* 5 seconds should be enough for initialization */
 alarm(to);
 
 n = init_uart(dev, u, send_break);
 if (n < 0) {
  perror(“Can’t initialize device”);
  exit(1);
 }

 alarm(0);

这段程序的目的:如果5秒内不能完成初始化串口的任务,我们将受到alarm信号触发的信息,一般是一条出错信息;

如果5秒内完成,则解除尽该信号;

while (1) sleep(999999999);

这个程序为什么不直接结束?

 

 

hciattach

——————————————————————————–

 

SYNOPSIS
       hciattach < tty > < type | id > [ speed ] [ flow ]

 

DESCRIPTION
       Hciattach is used to attach a serial UART to the Bluetooth stack as HCI
       transport interface.

       hciattach程序用来附着一个串口UART设备作为蓝牙协议栈的hci层传输接口。

OPTIONS
       <tty>  This specifies the serial device to attach. A leading  /dev  can
              be omitted. Examples: /dev/ttyS1 ttyS2
               定义使用哪一个串口设备。
       <type | id>
              The  type  or id of the Bluetooth device that is to be attached,
              i.e. vendor or other device specific identifier. Currently  sup-
              ported types are
               定义蓝牙通过串口连接的蓝牙设备的类型或者厂家ID。目前支持的类型有
              type   description

              any    Unspecified   HCI_UART   interface,  no  vendor  specific
                     options

              ericsson
                     Ericsson based modules

              digi   Digianswer based cards

              xircom Xircom PCMCIA cards: Credit Card Adapter  and  Real  Port
                     Adapter

              csr    CSR  Casira  serial  adapter  or BrainBoxes serial dongle
                     (BL642)

              bboxes BrainBoxes PCMCIA card (BL620)

              swave  Silicon Wave kits

              bcsp   Serial adapters using CSR chips with BCSP serial protocol
                     使用CSR蓝牙核,串口传输协议使用BCSP的串口蓝牙适配器。

       Supported IDs are (manufacturer id, product id)

              0×0105, 0×080a
                     Xircom  PCMCIA  cards:  Credit Card Adapter and Real Port
                     Adapter

              0×0160, 0×0002
                     BrainBoxes PCMCIA card (BL620)

       <speed>
              The speed specifies the UART speed to use. Baudrates higher than
              115.200bps  require vendor specific initializations that are not
              implemented for all types of devices. In general  the  following

       Written by Maxim Krasnyansky <maxk@qualcomm.com>

       man page by Nils Faerber <nils@kernelconcepts.de>

BlueZ                             Jan 22 2002                     HCIATTACH(8

2004年10月18日

sdp服务记录:

sdp服务器上有一个sdp服务记录数据库,

每一条服务记录由

服务句柄

服务属性1 //对于任意一个sdp服务记录,ServiceClassIDList
attribute是最重要的,它的属性值就是UUID

服务属性1的属性值

服务属性2

……

编写Linux下的Daemon程序 作者: dagger 无崖阁 xyg.ods.org 
版本: 1.0 2003-04-08 初始版本 
一、引言 Daemon程序是一直运行的服务端程序,又称为守护进程。
本文介绍了在Linux下编写Daemon程序的步骤,并给出了例子程序。 
二、Daemon程序简介 Daemon是长时间运行的进程,通常在系统启动后就运行,
在系统关闭时才结束。一般说Daemon程序在后台运行,是因为它没有控制终端,
无法和前台的用户交互。Daemon程序一般都作为服务程序使用,等待客户端程序
与它通信。我们也把运行的Daemon程序称作守护进程。 
三、Daemon程序编写规则 
编写Daemon程序有一些基本的规则,以避免不必要的麻烦。 
1、首先是程序运行后调用fork,并让父进程退出。子进程获得一个新的进程ID,
但继承了父进程的进程组ID。 
2、调用setsid创建一个新的session,使自己成为新session和新进程组的
leader,并使进程没有控制终端(tty)。 
3、改变当前工作目录至根目录,以免影响可加载文件系统。或者也可以改变到
某些特定的目录。 
4、设置文件创建mask为0,避免创建文件时权限的影响。 
5、关闭不需要的打开文件描述符。因为Daemon程序在后台执行,不需要于终端
交互,通常就关闭STDIN、STDOUT和STDERR。其它根据实际情况处理。 
另一个问题是Daemon程序不能和终端交互,也就无法使用printf方法输出信息
了。我们可以使用syslog机制来实现信息的输出,方便程序的调试。在使用
syslog前需要首先启动syslogd程序,关于syslogd程序的使用请参考它
的man page,或相关文档,我们就不在这里讨论了。 
四、一个Daemon程序的例子 编译运行环境为Redhat Linux 8.0。 
我们新建一个daemontest.c程序,文件内容如下: 
#include  #include  #include  #include  #include  
#include  #include  
int daemon_init(void) 
{ pid_t pid; 
if((pid = fork()) < 0) return(-1); 
else if(pid != 0) exit(0); /* parent exit */ 
/* child continues */ 
setsid(); /* become session leader */ 
chdir("/"); /* change working directory */ 
umask(0); /* clear file mode creation mask */ 
close(0); /* close stdin */ 
close(1); /* close stdout */ 
close(2); /* close stderr */ 
return(0); } 
void sig_term(int signo) 
{ if(signo == SIGTERM) 
/* catched signal sent by kill(1) command */ 
 { syslog(LOG_INFO, "program terminated."); 
 closelog(); exit(0); } 
} 
int main(void) 
{ if(daemon_init() == -1) 
{ printf("can't fork self\n"); exit(0); } 
openlog("daemontest", LOG_PID, LOG_USER); 
syslog(LOG_INFO, "program started."); 
signal(SIGTERM, sig_term); /* arrange to catch the signal */ 
while(1) { sleep(1); /* put your main program here */ } 
return(0); } 
使用如下命令编译该程序: gcc -Wall -o daemontest daemontest.c 
编译完成后生成名为daemontest的程序,执行./daemontest来测试程序的运行。
使用ps axj命令可以显示系统中已运行的daemon程序的信息,包括进程ID、
session ID、控制终端等内容。 
部分显示内容: 
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 
1098 1101 1101 1074 pts/1 1101 S 0 0:00 -bash 1 1581 777 777 ? -1 S 500 0:13 gedit 1 1650 1650 1650 ? -1 S 500 0:00 ./daemontest 794 1654 1654 794 pts/0 1654 R 500 0:00 
ps axj 从中可以看到daemontest程序运行的进程号为1650。
我们再来看看/var/log/messages文件中的信息: Apr 7 22:00:32 localhost 
daemontest[1650]: program started. 
显示了我们在程序中希望输出的信息。 
我们再使用kill 1650命令来杀死这个进程,
/var/log/messages文件中就会有如下的信息: 
Apr 7 22:11:10 localhost daemontest[1650]: program terminated.
 使用ps axj命令检查,发现系统中daemontest进程已经没有了。
 五、参考资料 
Advanced Programming in the UNIX Environment W.Richard Stevens 

void       g_main_loop_run        (GMainLoop    *loop)
{
 int open_max = sysconf(_SC_OPEN_MAX);
 struct pollfd *ufds = malloc(open_max * sizeof(struct pollfd));

 while (!loop->bail) {//外层主循环,本循环内没有函数改变loop的值,

//所以这是个无限循环;

//我猜测hcid->main.c中关于信号的处理将影响loop的值,

 memset(&sa, 0, sizeof(sa));
 sa.sa_flags = SA_NOCLDSTOP;
 sa.sa_handler = sig_term;
 //sigaction change default action by system when the first para signal happened;
 sigaction(SIGTERM, &sa, NULL);
 sigaction(SIGINT,  &sa, NULL);

sig_term的操作很简单loop->bail=1,这样g_main_loop_run就会退出无限循环;

比如,hcid是一个deamon,我们可以调用kill向hcid进程发出SIGTERM信号;
  int nfds, rc, i;
  struct watch *p, *w;

  nfds = 0;
  for (w = watch_head.next; w != NULL; w = w->next) {
   ufds[nfds].fd = w->channel->fd;
   ufds[nfds].events = w->condition;
   ufds[nfds].revents = 0;
   nfds++;
  }
  
  rc = poll(ufds, nfds, -1);

  if (rc < 0 && (errno == EINTR))

//poll’s man page http://jamesthornton.com/linux/man/poll.2.html

//EINTR A signal occurred before any requested event. 
   continue;

  if (rc < 0) {
   perror(“poll”);
   continue;
  }
 //poll的返回值大于0,是正常情况,我们处理;注意中文翻译书出错 
  p = &watch_head;
  w = watch_head.next;
  i = 0;
  while (w) {
   if (ufds[i].revents) {
    gboolean keep = w->func(w->channel, ufds[i].revents, w->user_data);
    if (!keep) {
      p->next = w->next;
      memset(w, 0, sizeof(*w));
      w = p->next;
      i++;
      continue;
    }
   } 
  
   p = w;
   w = w->next;
   i++;
  }

 }

 free(ufds);
}

Wait queues have several uses in the kernel, particularly for interrupt handling, process synchronization, and timing. Because these topics are discussed in later chapters, we’ll just say here that a process must often wait for some event to occur, such as for a disk operation to terminate, a system resource to be released, or a fixed interval of time to elapse.进程经常需要等待一些事件的发生,例如一个磁盘操作的结束,或者一个系统资源的释放等等。 Wait queues implement conditional waits on events: a process wishing to wait for a specific event places itself in the proper wait queue and relinquishes control.等待队列实现了事件上的条件等待:希望等待特定事件发生的进程将自己放在合适的队列并且放弃对cpu的控制。 Therefore, a wait queue represents a set of sleeping processes, which are woken up by the kernel when some condition becomes true. 因此,等待队列代表一个睡眠进程的集合,当条件为真时,由内核唤醒。

Wait queues are implemented as doubly linked lists whose elements include pointers to process descriptors. Each wait queue is identified by a wait queue head, a data structure of type wait_queue_head_t:

struct _ _wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};
typedef struct _ _wait_queue_head wait_queue_head_t;

Since wait queues are modified by interrupt handlers as well as by major kernel functions, the doubly linked lists must be protected from concurrent accesses, which could induce unpredictable results (see Chapter 5). Synchronization is achieved by the lock spin lock in the wait queue head.

Elements of a wait queue list are of type wait_queue_t:

struct _ _wait_queue {
    unsigned int flags;
    struct task_struct * task;
    struct list_head task_list;
};
typedef struct _ _wait_queue wait_queue_t;

Each element in the wait queue list represents a sleeping process, which is waiting for some event to occur; its descriptor address is stored in the task field. However, it is not always convenient to wake up all sleeping processes in a wait queue.

For instance, if two or more processes are waiting for exclusive access to some resource to be released, it makes sense to wake up just one process in the wait queue. This process takes the resource, while the other processes continue to sleep. (This avoids a problem known as the “thundering herd,” with which multiple processes are awoken only to race for a resource that can be accessed by one of them, and the result is that remaining processes must once more be put back to sleep.)

Thus, there are two kinds of sleeping processes: exclusive processes (denoted by the value 1 in the flags field of the corresponding wait queue element) are selectively woken up by the kernel, while nonexclusive processes (denoted by the value 0 in flags) are always woken up by the kernel when the event occurs. A process waiting for a resource that can be granted to just one process at a time is a typical exclusive process. Processes waiting for an event like the termination of a disk operation are nonexclusive.

the wake_up_process( ) function is used to make a process runnable. It sets the process state to TASK_RUNNING and invokes add_to_runqueue( ) to insert the process in the runqueue list. It also forces the invocation of the scheduler when the process has a dynamic priority larger than that of the current process or, in SMP systems, that of a process currently executing on some other CPU

这个函数的用途:可以是一个进程可运行。它设置进程的状态为TASH_RUNNING,并且调用add_to_runqueue函数将进程插入runqueue链表。当这个进程的优先级大于现在正在cpu上执行的进程,就会调用scheduler进程调度者。