分类: LINUX
2015-11-16 19:49:26
Chapter 4, Advanced Serial
Programming
第四章,高级串口编程
This chapter covers advanced serial programming techniques using the ioctl(2) and select(2) system calls.
Serial Port
IOCTLs
In Chapter 2, Configuring the Serial Port we used the tcgetattr and
tcsetattr functions to configure the serial port. Under UNIX these
functions use the ioctl(2) system call to do their magic. The ioctl
system call takes three arguments:
int ioctl(int fd, int request, ...);
The fd argument specifies the serial port file descriptor. The
request argument is a constant defined in the
本章主要谈论使用ioctl(2)和select(2)系统调用的高级串口编程技术。
串行端口
IOCTLs
在第二章,配置串口中我们使用了 tcgetattr 和
tcsetattr 函数来做串口的设置。在 UNIX 下,这些函数都是使用 ioctl(2) 系统调用来完成他们的任务的。ioctl
系统调用有如下三个参数:
int ioctl(int fd, int request, ...);
参数 fd 指定了串行端口的文件描述符。
参数 request 是一个定义在
Table 10 - IOCTL Requests for Serial Ports
表10 - IOCTL 的串口调用
Request |
Description |
POSIX Function |
TCGETS |
Gets the current serial port settings. 读取当前的串口属性 |
tcgetattr |
TCSETS |
Sets the serial port settings immediately 设置串口属性并立即生效 |
tcsetattr(fd, TCSANOW, &options) |
TCSETSF |
Sets the serial port settings after flushing the input and output buffers. 设置串口属性,等到输入输出缓冲区都清空了再生效 |
tcsetattr(fd, TCSAFLUSH, &options) |
TCSETSW |
Sets the serial port settings after allowing the input and output buffers to drain/empty. 设置串口属性,等到允许清空输入输出缓冲区了或数据传完后设置生效 |
tcsetattr(fd, TCSADRAIN, &options) |
TCSBRK |
Sends a break for the given time. 在指定时间后发送break |
tcsendbreak, tcdrain |
TCXONC |
Controls software flow control. 控制软件流控 |
tcflow |
TCFLSH |
Flushes the input and/or output queue. 将输入输出队列全部发出 |
tcflush |
TIOCMGET |
Returns the state of the "MODEM" bits. 返回 “MODEM” 位的状态 |
None |
TIOCMSET |
Sets the state of the "MODEM" bits. 设置“MODEM”位的状态 |
None |
FIONREAD |
Returns the number of bytes in the input buffer. 返回输入缓冲区内的字节数 |
None |
Getting the Control
Signals
The TIOCMGET ioctl gets the current "MODEM" status bits, which
consist of all of the RS-232 signal lines except RXD and TXD,
listed in Table 11.
To get the status bits, call ioctl with a pointer to an integer to
hold the bits, as shown in Listing 5.
获得控制信号
TIOCMGET -
ioctl 获得当前“MODEM”的状态位,其中包括了除 RXD 和 TXD 之外,所有的RS-232 信号线,见列表
11。
为了获得状态位,使用一个包含比特位的整数的指针来调用 ioctl,见清单5。
Listing 5 - Getting the
MODEM status bits.
清单 5 - 读取 DODEM 的状态位.
#include
#include
int fd;
int status;
ioctl(fd, TIOCMGET, &status);
Table 11 - Control Signal Constants
表 11 - 控制信号常量
Constant |
Description |
TIOCM_LE |
DSR (data set ready/line enable) |
TIOCM_DTR |
DTR (data terminal ready) |
TIOCM_RTS |
RTS (request to send) |
TIOCM_ST |
Secondary TXD (transmit) |
TIOCM_SR |
Secondary RXD (receive) |
TIOCM_CTS |
CTS (clear to send) |
TIOCM_CAR |
DCD (data carrier detect) |
TIOCM_CD |
Synonym for TIOCM_CAR |
TIOCM_RNG |
RNG (ring) |
TIOCM_RI |
Synonym for TIOCM_RNG |
TIOCM_DSR |
DSR (data set ready) |
Setting the Control
Signals
The TIOCMSET ioctl sets the "MODEM" status bits defined above. To
drop the DTR signal you can use the code in Listing 6.
设置控制信号
TIOCMSET -
ioctl 设置“MODEM”上述定义的状态位。可以使用 清单6 的代码来给DTR信号置低。
Listing 6 - Dropping DTR with the
TIOCMSET ioctl.
清单 6 - 使用 TIOCMSET ioctl 置低 DTR 信号
#include
#include
int fd; int status;
ioctl(fd, TIOCMGET, &status);
status &= ~TIOCM_DTR;
ioctl(fd, TIOCMSET, &status);
The bits that can be set depend on the operating system, driver, and modes in use. Consult your operating system documentation for more information.
能进行设置的比特位有操作系统,驱动以及使用的模式决定。查询你的操作系统的档案可以获取更多的信息。
Getting the Number of Bytes
Available
The FIONREAD ioctl gets the number of bytes in the serial port
input buffer. As with TIOCMGET you pass in a pointer to an integer
to hold the number of bytes, as shown in Listing 7.
获取可供读取的字节数
FIONREAD
- ioctl 读取串行端口输入缓冲区中的字节数。与 TIOCMGET 一起传递一个包含字节数的整数的指针,如 清单7 所示。
Listing 7 - Getting the number of
bytes in the input buffer.
清单7 - 读取串行端口输入缓冲区中的字节数
#include
#include
int fd;
int bytes;
ioctl(fd, FIONREAD, &bytes);
This can be useful when polling a serial port for data, as your
program can determine the number of bytes in the input buffer
before attempting a read.
在查询串口是否有数据到来的时候这一段是很有用的,可以让程序在准备读取之前用来确定输入缓冲区里面的可读字节数。
Selecting Input from a Serial
Port
While simple applications can poll or
wait on data coming from the serial port, most applications are not
simple and need to handle input from multiple sources.
UNIX provides this capability through the select(2) system call.
This system call allows your program to check for input, output, or
error conditions on one or more file descriptors. The file
descriptors can point to serial ports, regular files, other
devices, pipes, or sockets. You can poll to check for pending
input, wait for input indefinitely, or timeout after a specific
amount of time, making the select system call extremely
flexible.
Most GUI Toolkits provide an interface to select; we will discuss
the X Intrinsics ("Xt") library later in this chapter.
从串行端口选择输入
虽然简单的程序可以通过poll串口或者等待串口数据到来读取串口,大多数的程序却并不这么简单,有时候需要处理来自多个源的输入。
UNIX 通过 select(2)
系统调用提供了这种能力。这个系统调用允许你的程序从一个或多个文件描述符获取输入/输出或者错误信息。文件描述符可以指向串口,普通文件,其他设备,管道,或者socket
。你可以查询待处理的输入,等待不确定的输入,或者在一指定的超时到达后超时退出,这些都使得 select
系统调用非常的灵活。
大多数的 GUI 工具提供一个借口指向 select,我们会在本章后面的部分讨论 X Intrinsics 库(“Xt”)。
The SELECT System Call
The select system call accepts 5 arguments:
int select(int max_fd, fd_set *input, fd_set *output, fd_set
*error, struct timeval *timeout);
The max_fd argument specifies the highest numbered file descriptor
in the input, output, and error sets. The input, output, and error
arguments specify sets of file descriptors for pending input,
output, or error conditions; specify NULL to disable monitoring for
the corresponding condition. These sets are initialized using three
macros:
FD_ZERO(fd_set);
FD_SET(fd, fd_set);
FD_CLR(fd, fd_set);
The FD_ZERO macro clears the set entirely. The FD_SET and FD_CLR
macros add and remove a file descriptor from the set,
respectively.
The timeout argument specifies a timeout value which consists of
seconds (timeout.tv_sec) and microseconds (timeout.tv_usec). To
poll one or more file descriptors, set the seconds and microseconds
to zero. To wait indefinitely specify NULL for the timeout
pointer.
The select system call returns the number of file descriptors that
have a pending condition, or -1 if there was an error.
SELECT 系统调用
Select 系统调用可以接受 5 个参数:
int select(int max_fd, fd_set *input, fd_set *output, fd_set
*error, struct timeval *timeout);
参数 max_fd 定义了所有用到的文件描述符(input, output, error集合)中的最大值。
参数 input, output, error 定义了待处理的输入,输出,错误情况;置 NULL
则代表不去监查相应的条件。这几个集合用三个宏来进行初始化
FD_ZERO(fd_set);
FD_SET(fd, fd_set);
FD_CLR(fd, fd_set);
宏 FD_ZERO 清空整个集合; FD_SET 和 FD_CLR 分别从集合中添加、删除文件描述符。
参数 timeout
定义了一个由秒(timeout.tv_sec)和毫秒(timeout.tv_usec)组成的一个超时值。要查询一个或多个文件描述符,把秒和毫秒置为
0 。要无限等待的话就把 timeout 指针置 NULL 。
select 系统调用返回那个有待处理条件的文件描述符的值,或者,如果有错误发生的话就返回 -1。
Using the SELECT System Call
Suppose
we are reading data from a serial port and a socket. We want to
check for input from either file descriptor, but want to notify the
user if no data is seen within 10 seconds. To do this we'll need to
use the select system call, as shown in Listing 8.
使用 select 系统调用
假设我们正在从一个串口和一个socket
读取数据。我们想确认来自各文件描述符的输入,又希望如果10秒钟内都没有数据的话通知用户。要完成这些工作,我们可以像 清单8
这样使用select系统调用:
Listing 8 - Using SELECT to process input from more than one
source.
清单8 - 使用select 处理来自多个源的输入
#include
#include
#include
#include
int n;
int socket;
int fd;
int max_fd;
fd_set input;
struct timeval timeout;
FD_ZERO(input);
FD_SET(fd, input);
FD_SET(socket, input);
max_fd = (socket > fd ? socket : fd) + 1;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
n = select(max_fd, &input, NULL, NULL,
&timeout);
if (n < 0)
perror("select failed");
else if (n == 0)
puts("TIMEOUT");
else {
if (FD_ISSET(fd, input))
process_fd();
if (FD_ISSET(socket, input))
process_socket();
}
You'll notice that we first check the return value of the select
system call. Values of 0 and -1 yield the appropriate warning and
error messages. Values greater than 0 mean that we have data
pending on one or more file descriptors.
To determine which file descriptor(s) have pending input, we use
the FD_ISSET macro to test the input set for each file descriptor.
If the file descriptor flag is set then the condition exists (input
pending in this case) and we need to do something.
你会发现,我们首先检查select系统调用的返回值。如果是 0 和 -1 则表示相应的警告和错误信息。大于 0
的值代表我们在一个或多个文件描述符上有待处理的数据。
要知道是哪个文件描述符有待处理的输入,我们要使用 FDISSET
宏来测试每个文件描述符的输入集合。如果文件描述符标志被设置了,则符合条件(这时候就有待处理的输入了)我们需要进行一些操作。
Using SELECT with the X Intrinsics
Library
The X Intrinsics library provides an
interface to the select system call via the XtAppAddInput(3x) and
XtAppRemoveInput(3x) functions:
int XtAppAddInput(XtAppContext context, int fd, int mask,
XtInputProc proc, XtPointer data);
void XtAppRemoveInput(XtAppContext context, int input);
The select system call is used internally to implement timeouts,
work procedures, and check for input from the X server. These
functions can be used with any Xt-based toolkit including Xaw,
Lesstif, and Motif.
The proc argument to XtAppAddInput specifies the function to call
when the selected condition (e.g. input available) exists on the
file descriptor. In the previous example you could specify the
process_fd or process_socket functions.
Because Xt limits your access to the select system call, you'll
need to implement timeouts through another mechanism, probably via
XtAppAddTimeout(3x).
在X Intrinsics library
中使用select
X Intrinsics library 提供了一个接口,通过 XtAppAddInput(3x) 和
XtAppRemoveInput(3x) 函数来使用 select 系统调用。
int XtAppAddInput(XtAppContext context, int fd, int mask,
XtInputProc proc, XtPointer data);
void XtAppRemoveInput(XtAppContext context, int input);
select 系统调用是用来实现内部的超时,工作过程,并且检查来自 X Server 的输入。这些函数可以在任何 基于Xt
的工具上使用,包括Xaw, Lesstif, 和 Motif。
XtAppAddInput 的参数 proc
定义了当某一个文件描述符上的select条件满足时函数的调用(比如有输入进来)。在之前的例子里,你可以定义process_fd 或
processs_socket 函数。
由于 Xt 限制了你对 select 系统调用的访问,你需要通过其他机制实现超时,可以通过
XtAppAddTimeout(3x)。
————————————————————————————————————
关于fd_set:
select()函数主要是建立在fd_set类型的基础上的。fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个宏来操作:
fd_set set;
FD_ZERO(&set);
FD_SET(fd, &set);
FD_CLR(fd, &set);
FD_ISSET(fd, &set);
过去,一个fd_set通常只能包含<32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd;现
在,UNIX系统通常会在头文件
fd_set set;
FD_ZERO(&set);
FD_SET(0, &set);
FD_CLR(4, &set);
FD_ISSET(5, &set);
―――――――――――――――――――――――――――――――――――――――
注意fd的最大值必须
――――――――――――――――――――――――――――――――――――――― select函数的接口比较简单: int select(int nfds, fd_set
*readset, fd_set *writeset, fd_set* exceptset, struct tim
*timeout); 功能:
测试指定的fd可读?可写?有异常条件待处理? 参数: nfds: 需要检查的文件描述字个数(即检查到fd_set的第几位,因为打开一个文件是返回的文件句柄总是从0开始的,因此也可以说检查到fd_set的前几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所
有1024位。 readset:
用来检查可读性的一组文件描述字。 writeset: 用来检查可写性的一组文件描述字。
exceptset:用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内) timeout:有三种可能:
1:timeout=NULL(阻塞:直到有一个fd位被置为1函数才返回)
2:timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)
3. timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回)
返回值: 返回对应位仍然为1的fd的总数。 Remarks:
三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1。 使用select函数的过程一般是:
先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数
select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。 以下是一个测试单个文件描述字可读性(fd_set中相应位置1,就是在说对应的这个文件是可读的)的例子: int isready(int fd) {
int rc;
fd_set
fds;
struct
tim
tv;
FD_ZERO(&fds);
FD_SET(fd,&fds);
tv.tv_sec
= tv.tv_usec =
0;
rc
= select(fd+1, &fds, NULL, NULL,
&tv);
//第一个参数设为三组fd_set中最大的fd+1,这里只有一个文件,自然就是这个文件的fd+1
if
(rc <
0) //error
return
-1;
return
FD_ISSET(fd,&fds) ? 1 : 0; } 下面还有一个复杂一些的应用:
//这段代码将指定测试Socket的描述字的可读可写性,因为Socket使用的也是fd uint32 SocketWait(TSocket *s,bool
rd,bool wr,uint32
timems) {
fd_set rfds,wfds; #ifdef
_WIN32
TIM tv; #else
struct tim tv;
#endif
FD_ZERO(&rfds);
FD_ZERO(&wfds);
if
(rd) //TRUE
FD_SET(*s,&rfds);
//添加要测试的描述字
if
(wr)
//FALSE
FD_SET(*s,&wfds);
tv.tv_sec=timems/1000;
//second
tv.tv_usec=timems00;
//ms
for (;;) //如果errno==EINTR,反复测试缓冲区的可读性
switch(select((*s)+1,&rfds,&wfds,NULL,
(timems==TIME_INFINITE?NULL:&tv)))
//测试在规定的时间内套接口接收缓冲区中是否有数据可读
{
//0--超时,-1--出错
case
0:
return 0;
case
(-1):
if (SocketError()==EINTR)
break;
return 0; //有错但不是EINTR
default:
if (FD_ISSET(*s,&rfds)) //如果s是fds中的一员返回非0,否则返回0
return 1;
if (FD_ISSET(*s,&wfds))
return 2;
return 0;
}; }