这部分是相当重要的一部分,之前在工作项目中有用到过,特意认真的看过。文章最后会把我项目用到部分的源码贴出。
再有值得纪念的一下,原创文章数终于赶上转载文章数了。说明我找到的学习方法是对了,一开始茫然不知所措时,学习是做加法,将看到的好的文章转载;但到了一定阶段,要懂得做减法了,将之前转载部分系统的分类总结;最后就是举一反三,将难题,转化成自己熟悉的问题解决。
文章开头总是很难的,要讲的东西太多,不知道从哪下笔。我就以下面参看这篇文章为基础开讲:
参看:select、poll、epoll之间的区别总结[整理]
/* According to POSIX.1-2001 */
#include
/* According to earlier standards */
#include
#include
#include
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
返回值:准备就绪的描述符数目:若超时,返回 0;若出错,返回 -1
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
timeout 的取值有以下 3 中情况:
void FD_CLR(int fd, fd_set *set); //
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
这些接口可实现为宏或函数。调用 FD_ZERO 将一个 fd_set 变量的所有位设置为 0。要开启描述符集中的一位,可以调用 FD_SET。调用 FD_CLR 可以清除一位。最后,可以调用 FD_ISSET 测试描述符集中的一个指定位是否已打开。
fd_set rset;
int fd;
FD_ZERO (&rset);
FD_SET (fd, &rset);
FD_SET (STDIN_FILEND, &rset);
从 select 返回时,可以用 FD_ISSET 测试该集中的一个给定位是否仍处于打开状态。
if (FD_ISSET (fd, &rset))
{
....
}
select 的中间 3 个参数(指向描述符集的指针)中的任意一个(或全部)可以是空指针,这表示对相应条件并不关心。如果所有 3 个指针都是 NULL,则 select 提供了比 sleep 更精确的定时器。
root@ubuntu:/usr/include# grep "FD_SETSIZE" * -rn
i386-linux-gnu/sys/select.h:79:#define FD_SETSIZE __FD_SETSIZE
i386-linux-gnu/bits/typesizes.h:63:#define __FD_SETSIZE 1024
EBADF An invalid file descriptor was given in one of the sets. (Perhaps a file descriptor that was already closed, or one on which
an error has occurred.)
EINTR A signal was caught; see signal(7).
EINVAL nfds is negative or the value contained within timeout is invalid.
ENOMEM unable to allocate memory for internal tables.
示例一:用来循环读取键盘输入
#include
#include
#include
#include
#include
#include
#include
#include
int main (void)
{
int keyboard;
int ret, i;
char c;
fd_set readfd;
struct timeval timeout;
//打开 /dev/tty 只读非阻塞
keyboard = open ("/dev/tty", O_RDONLY | O_NONBLOCK);
//assert 断言宏
assert (keyboard > 0);
while (1)
{
timeout.tv_sec = 5;
timeout.tv_usec = 0;
FD_ZERO (&readfd);
FD_SET (keyboard, &readfd);
ret = select (keyboard + 1, &readfd, NULL, NULL, &timeout);
//select error when ret = -1
if (ret == -1)
perror ("select error");
//data coming when ret>0
else if (ret)
{
if (FD_ISSET (keyboard, &readfd))
{
i = read (keyboard, &c, 1);
if ('\n' == c)
continue;
printf ("the input is %c\n", c);
if ('q' == c)
break;
}
}
//time out when ret = 0
else if (ret == 0)
printf ("time out\n");
}
}
输出结果:
(5秒内操作的话)
s
the input is s
d
the input is d
f
the input is f
q
the input is q
(结束)
(5秒内不操作的话)
time out
time out
time out
time out
//示例二:通过select系统调用进行io多路切换,实现异步读取串口数据
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define FALSE -1
#define TRUE 0
#define flag 1
#define noflag 0
int wait_flag = noflag;
int STOP = 0;
int res;
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 (fd, TCSANOW, &Opt);
if (status != 0)
{
perror ("tcsetattr fd1");
return;
}
tcflush (fd, TCIOFLUSH);
}
}
}
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\n");
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\n");
return (FALSE);
}
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
fprintf (stderr, "Unsupported stop bits\n");
return (FALSE);
}
/* Set input parity option */
if (parity != 'n')
options.c_iflag |= INPCK;
tcflush (fd, TCIFLUSH);
options.c_cc[VTIME] = 150;
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);
}
void
signal_handler_IO (int status)
{
printf ("received SIGIO signale.\n");
wait_flag = noflag;
}
int
main ()
{
printf ("This program updates last time at %s %s\n", __TIME__, __DATE__);
printf ("STDIO COM1\n");
int fd;
fd = open ("/dev/ttyUSB0", O_RDWR);
if (fd == -1)
{
perror ("serialport error\n");
}
else
{
printf ("open ");
printf ("%s", ttyname (fd));
printf (" succesfully\n");
}
set_speed (fd, 115200);
if (set_Parity (fd, 8, 1, 'N') == FALSE)
{
printf ("Set Parity Error\n");
exit (0);
}
char buf[255];
fd_set rd;
int nread = 0;
while(1)
{
FD_ZERO(&rd);
FD_SET(fd, &rd);
while(FD_ISSET(fd, &rd))
{
if(select(fd+1, &rd, NULL,NULL,NULL) < 0)
{
perror("select error\n");
}
else
{
while((nread = read(fd, buf, sizeof(buf))) > 0)
{
printf("nread = %d,%s\n",nread, buf);
printf("test\n");
memset(buf, 0 , sizeof(buf));
}
}
}
}
close (fd);
return 0;
}
fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset){……}
通过 select 返回值,做判断语句。//代码一:循环读取数据
#include
#include
#include
#include
#include
#include
#include
#include
#define FALSE -1
#define TRUE 0
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(fd, TCSANOW, &Opt);
if (status != 0) {
perror("tcsetattr fd1");
return;
}
tcflush(fd,TCIOFLUSH);
}
}
}
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\n"); 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\n");
return (FALSE);
}
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (FALSE);
}
/* Set input parity option */
if (parity != 'n')
options.c_iflag |= INPCK;
tcflush(fd,TCIFLUSH);
options.c_cc[VTIME] = 150;
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);
}
int main()
{
printf("This program updates last time at %s %s\n",__TIME__,__DATE__);
printf("STDIO COM1\n");
int fd;
fd = open("/dev/ttyS0",O_RDWR);
if(fd == -1)
{
perror("serialport error\n");
}
else
{
printf("open ");
printf("%s",ttyname(fd));
printf(" succesfully\n");
}
set_speed(fd,115200);
if (set_Parity(fd,8,1,'N') == FALSE) {
printf("Set Parity Error\n");
exit (0);
}
char buf[] = "fe55aa07bc010203040506073d";
write(fd,&buf,26);
char buff[512];
int nread;
while(1)
{
if((nread = read(fd, buff, 512))>0)
{
printf("\nLen: %d\n",nread);
buff[nread+1] = '\0';
printf("%s",buff);
}
}
close(fd);
return 0;
}
//代码二:通过signal机制读取数据
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define FALSE -1
#define TRUE 0
#define flag 1
#define noflag 0
int wait_flag = noflag;
int STOP = 0;
int res;
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 (fd, TCSANOW, &Opt);
if (status != 0)
{
perror ("tcsetattr fd1");
return;
}
tcflush (fd, TCIOFLUSH);
}
}
}
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\n");
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\n");
return (FALSE);
}
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
fprintf (stderr, "Unsupported stop bits\n");
return (FALSE);
}
/* Set input parity option */
if (parity != 'n')
options.c_iflag |= INPCK;
tcflush (fd, TCIFLUSH);
options.c_cc[VTIME] = 150;
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);
}
void
signal_handler_IO (int status)
{
printf ("received SIGIO signale.\n");
wait_flag = noflag;
}
int
main ()
{
printf ("This program updates last time at %s %s\n", __TIME__, __DATE__);
printf ("STDIO COM1\n");
int fd;
struct sigaction saio;
fd = open ("/dev/ttyUSB0", O_RDWR);
if (fd == -1)
{
perror ("serialport error\n");
}
else
{
printf ("open ");
printf ("%s", ttyname (fd));
printf (" succesfully\n");
}
saio.sa_handler = signal_handler_IO;
sigemptyset (&saio.sa_mask);
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction (SIGIO, &saio, NULL);
//allow the process to receive SIGIO
fcntl (fd, F_SETOWN, getpid ());
//make the file descriptor asynchronous
fcntl (fd, F_SETFL, FASYNC);
set_speed (fd, 115200);
if (set_Parity (fd, 8, 1, 'N') == FALSE)
{
printf ("Set Parity Error\n");
exit (0);
}
char buf[255];
while (STOP == 0)
{
usleep (100000);
/* after receving SIGIO ,wait_flag = FALSE,input is availabe and can be read */
if (wait_flag == 0)
{
memset (buf, 0, sizeof(buf));
res = read (fd, buf, 255);
printf ("nread=%d,%s\n", res, buf);
// if (res ==1)
// STOP = 1; /*stop loop if only a CR was input */
wait_flag = flag; /*wait for new input */
}
}
close (fd);
return 0;
}
int do_select(int n, fd_set_bits *fds, s64 *timeout)
{
struct poll_wqueues table;
poll_table *wait;
int retval, i;
rcu_read_lock();
retval = max_select_fd(n, fds);
rcu_read_unlock();
if (retval < 0)
return retval;
n = retval;
poll_initwait(&table);
wait = &table.pt;
if (!*timeout)
wait = NULL;
retval = 0; //retval用于保存已经准备好的描述符数,初始为0
for (;;) {
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
long __timeout;
set_current_state(TASK_INTERRUPTIBLE); //将当前进程状态改为TASK_INTERRUPTIBLE
inp = fds->in; outp = fds->out; exp = fds->ex;
rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
for (i = 0; i < n; ++rinp, ++routp, ++rexp) { //遍历每个描述符
unsigned long in, out, ex, all_bits, bit = 1, mask, j;
unsigned long res_in = 0, res_out = 0, res_ex = 0;
const struct file_operations *f_op = NULL;
struct file *file = NULL;
in = *inp++; out = *outp++; ex = *exp++;
all_bits = in | out | ex;
if (all_bits == 0) {
i += __NFDBITS; // //如果这个字没有待查找的描述符, 跳过这个长字(32位)
continue;
}
for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) { //遍历每个长字里的每个位
int fput_needed;
if (i >= n)
break;
if (!(bit & all_bits))
continue;
file = fget_light(i, &fput_needed);
if (file) {
f_op = file->f_op;
MARK(fs_select, "%d %lld",
i, (long long)*timeout);
mask = DEFAULT_POLLMASK;
if (f_op && f_op->poll)
/* 在这里循环调用所监测的fd_set内的所有文件描述符对应的驱动程序的poll函数 */
mask = (*f_op->poll)(file, retval ? NULL : wait);
fput_light(file, fput_needed);
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit; //如果是这个描述符可读, 将这个位置位
retval++; //返回描述符个数加1
}
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
}
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
}
}
cond_resched();
}
//返回结果
if (res_in)
*rinp = res_in;
if (res_out)
*routp = res_out;
if (res_ex)
*rexp = res_ex;
}
wait = NULL;
/* 到这里遍历结束。retval保存了检测到的可操作的文件描述符的个数。如果有文件可操作,则跳出for(;;)循环,直接返回。若没有文件可操作且timeout时间未到同时没有收到signal,则执行schedule_timeout睡眠。睡眠时间长短由__timeout决定,一直等到该进程被唤醒。
那该进程是如何被唤醒的?被谁唤醒的呢?
我们看下面的select唤醒过程*/
if (retval || !*timeout || signal_pending(current))
break;
if(table.error) {
retval = table.error;
break;
}
if (*timeout < 0) {
/* Wait indefinitely */
__timeout = MAX_SCHEDULE_TIMEOUT;
} else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT - 1)) {
/* Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in a loop */
__timeout = MAX_SCHEDULE_TIMEOUT - 1;
*timeout -= __timeout;
} else {
__timeout = *timeout;
*timeout = 0;
}
__timeout = schedule_timeout(__timeout);
if (*timeout >= 0)
*timeout += __timeout;
}
__set_current_state(TASK_RUNNING);
poll_freewait(&table);
return retval;
}
(摘自《Linux Device Drivers – ThirdEdition》Page 165)
static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = 0;
/*
* The buffer is circular; it is considered full
* if "wp" is right behind "rp" and empty if the
* two are equal.
*/
down(&dev->sem);
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if (dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM; /* readable */
if (spacefree(dev))
mask |= POLLOUT | POLLWRNORM; /* writable */
up(&dev->sem);
return mask;
}
将用户进程插入驱动的等待队列是通过poll_wait做的。static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && wait_address)
p->qproc(filp, wait_address, p);
}
这里的p->qproc在do_select内poll_initwait(&table)被初始化为__pollwait,如下:void poll_initwait(struct poll_wqueues *pwq)
{
init_poll_funcptr(&pwq->pt, __pollwait);
pwq->error = 0;
pwq->table = NULL;
pwq->inline_index = 0;
}
__pollwait定义如下:/* Add a new entry */
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table *p)
{
struct poll_table_entry *entry = poll_get_entry(p);
if (!entry)
return;
get_file(filp);
entry->filp = filp;
entry->wait_address = wait_address;
init_waitqueue_entry(&entry->wait, current);
add_wait_queue(wait_address,&entry->wait);
}
通过 init_waitqueue_entry 初始化一个等待队列项,这个等待队列项关联的进程即当前调用 select 的进程。然后将这个等待队列项插入等待队列 wait_address。Wait_address 即在驱动 poll 函数内调用 poll_wait(filp, &dev->inq, wait);时传入的该驱动的 &dev->inq 或者 &dev->outq 等待队列。struct tty_struct {
……
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
……
}
当uart设备接收到数据,会调用tty_flip_buffer_push(tty);将收到的数据push到tty层的buffer。serial8250_interrupt -> serial8250_handle_port -> receive_chars -> tty_flip_buffer_push ->
flush_to_ldisc -> disc->receive_buf
在disc->receive_buf函数内:
if (waitqueue_active(&tty->read_wait)) //若有进程阻塞在read_wait上则唤醒
wake_up_interruptible(&tty->read_wait);
到这里明白了select进程被唤醒的过程。由于该进程是阻塞在所有监测的文件对应的设备等待队列上的,因此在timeout时间内,只要任意个设备变为可操作,都会立即唤醒该进程,从而继续往下执行。这就实现了select的当有一个文件描述符可操作时就立即唤醒执行的基本原理。
#include
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
pselect 的超时值被声明为 const,这保证了调用 pselect 不会改变此值。
#include
#include
#include
#include
#include
#include
#define BUFFSIZE 80
void sig_int(int signo);
void err_sys(const char *p_error);
void sig_alrm(int signo)
{
char s[] = "receive";
psignal(signo, s);
return;
}
int
main(int argc, char **argv)
{
int maxfdp1;
fd_set rset;
sigset_t sigmask;
ssize_t nread;
char buf[BUFFSIZE];
sigset_t sigset;
struct sigaction act;
// set SIGALRM signal handler
act.sa_handler = sig_alrm;
if (sigemptyset(&act.sa_mask) == -1)
err_sys("sigemptyset");
act.sa_flags = 0;
if (sigaction(SIGALRM, &act, NULL) == -1)
err_sys("sigaction");
// initialize signal set and addition SIGALRM into sigset
if (sigemptyset(&sigset) == -1)
err_sys("sigemptyet");
if (sigaddset(&sigset, SIGALRM) == -1)
err_sys("sigaddset");
alarm(1);
FD_ZERO(&rset);
FD_SET(STDIN_FILENO, &rset);
maxfdp1 = STDIN_FILENO + 1;
if (pselect(maxfdp1, &rset, NULL, NULL, NULL, &sigset) <= 0)
err_sys("pselect error");
if (FD_ISSET(STDIN_FILENO, &rset))
{
if ((nread = read(STDIN_FILENO, buf, BUFFSIZE)) == -1)
err_sys("read error");
if (write(STDOUT_FILENO, buf, nread) != nread)
err_sys("write error");
}
exit(0);
}
void
sig_int(int signo)
{
char s[] = "received";
psignal(signo, s);
return;
}
void
err_sys(const char *p_error)
{
perror(p_error);
exit(1);
}
输出结果:
d
receive: Alarm clock
d
上段代码如果没有 CTRL+C 送上一个 SIGINT 信号,将永远阻塞在与用户的交互上,ALARM 产生的 SIGALRM 信号永远打断不了 PSELECT, ALARM 信号被成功屏蔽。
#include
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
返回值:准备就绪的描述符数目;若超时,返回 0;若出错,返回 -1.
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
fds 数组中的元素数由 nfds 指定。
/* These are specified by iBCS2 */
#define POLLIN 0x0001
#define POLLPRI 0x0002
#define POLLOUT 0x0004
#define POLLERR 0x0008
#define POLLHUP 0x0010
#define POLLNVAL 0x0020
/* The rest seem to be more-or-less nonstandard. Check them! */
#define POLLRDNORM 0x0040
#define POLLRDBAND 0x0080
#ifndef POLLWRNORM
#define POLLWRNORM 0x0100
#endif
#ifndef POLLWRBAND
#define POLLWRBAND 0x0200
#endif
EBADF 一个或多个结构体中指定的文件描述符无效。
EFAULTfds 指针指向的地址超出进程的地址空间。
EINTR 请求的事件之前产生一个信号,调用可以重新发起。
EINVALnfds 参数超出PLIMIT_NOFILE值。
ENOMEM 可用内存不足,无法完成请求。
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_BUFFER_SIZE 1024
#define IN_FILES 3
#define TIME_DELAY 60*5
#define MAX(a,b) ((a>b)?(a):(b))
int main(int argc ,char **argv)
{
struct pollfd fds[IN_FILES];
char buf[MAX_BUFFER_SIZE];
int i,res,real_read, maxfd;
fds[0].fd = 0;
if((fds[1].fd=open("data1",O_RDONLY|O_NONBLOCK)) < 0)
{
fprintf(stderr,"open data1 error:%s",strerror(errno));
return 1;
}
if((fds[2].fd=open("data2",O_RDONLY|O_NONBLOCK)) < 0)
{
fprintf(stderr,"open data2 error:%s",strerror(errno));
return 1;
}
for (i = 0; i < IN_FILES; i++)
{
fds[i].events = POLLIN;
}
while(fds[0].events || fds[1].events || fds[2].events)
{
if (poll(fds, IN_FILES, TIME_DELAY) <= 0)
{
printf("Poll error\n");
return 1;
}
for (i = 0; i< IN_FILES; i++)
{
if (fds[i].revents)
{
memset(buf, 0, MAX_BUFFER_SIZE);
real_read = read(fds[i].fd, buf, MAX_BUFFER_SIZE);
if (real_read < 0)
{
if (errno != EAGAIN)
{
return 1;
}
}
else if (!real_read)
{
close(fds[i].fd);
fds[i].events = 0;
}
else
{
if (i == 0)
{
if ((buf[0] == 'q') || (buf[0] == 'Q'))
{
return 1;
}
}
else
{
buf[real_read] = '\0';
printf("%s", buf);
}
}
}
}
}
exit(0);
}
创建:
data1 1234567890
data2 abcdefghij
输出结果:
1234567890
abcdefghij
#include
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的 fd
//保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
//感兴趣的事件和被触发的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXEVENTS 64
//函数:
//功能:创建和绑定一个TCP socket
//参数:端口
//返回值:创建的socket
static int
create_and_bind (char *port)
{
struct addrinfo hints;
struct addrinfo *result, *rp;
int s, sfd;
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */
hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
hints.ai_flags = AI_PASSIVE; /* All interfaces */
s = getaddrinfo (NULL, port, &hints, &result);
if (s != 0)
{
fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
return -1;
}
for (rp = result; rp != NULL; rp = rp->ai_next)
{
sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1)
continue;
s = bind (sfd, rp->ai_addr, rp->ai_addrlen);
if (s == 0)
{
/* We managed to bind successfully! */
break;
}
close (sfd);
}
if (rp == NULL)
{
fprintf (stderr, "Could not bind\n");
return -1;
}
freeaddrinfo (result);
return sfd;
}
//函数
//功能:设置socket为非阻塞的
static int
make_socket_non_blocking (int sfd)
{
int flags, s;
//得到文件状态标志
flags = fcntl (sfd, F_GETFL, 0);
if (flags == -1)
{
perror ("fcntl");
return -1;
}
//设置文件状态标志
flags |= O_NONBLOCK;
s = fcntl (sfd, F_SETFL, flags);
if (s == -1)
{
perror ("fcntl");
return -1;
}
return 0;
}
//端口由参数argv[1]指定
int
main (int argc, char *argv[])
{
int sfd, s;
int efd;
struct epoll_event event;
struct epoll_event *events;
if (argc != 2)
{
fprintf (stderr, "Usage: %s [port]\n", argv[0]);
exit (EXIT_FAILURE);
}
sfd = create_and_bind (argv[1]);
if (sfd == -1)
abort ();
s = make_socket_non_blocking (sfd);
if (s == -1)
abort ();
s = listen (sfd, SOMAXCONN);
if (s == -1)
{
perror ("listen");
abort ();
}
//除了参数size被忽略外,此函数和epoll_create完全相同
efd = epoll_create1 (0);
if (efd == -1)
{
perror ("epoll_create");
abort ();
}
event.data.fd = sfd;
event.events = EPOLLIN | EPOLLET;//读入,边缘触发方式
s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);
if (s == -1)
{
perror ("epoll_ctl");
abort ();
}
/* Buffer where events are returned */
events = calloc (MAXEVENTS, sizeof event);
/* The event loop */
while (1)
{
int n, i;
n = epoll_wait (efd, events, MAXEVENTS, -1);
for (i = 0; i < n; i++)
{
if ((events[i].events & EPOLLERR) ||
(events[i].events & EPOLLHUP) ||
(!(events[i].events & EPOLLIN)))
{
/* An error has occured on this fd, or the socket is not
ready for reading (why were we notified then?) */
fprintf (stderr, "epoll error\n");
close (events[i].data.fd);
continue;
}
else if (sfd == events[i].data.fd)
{
/* We have a notification on the listening socket, which
means one or more incoming connections. */
while (1)
{
struct sockaddr in_addr;
socklen_t in_len;
int infd;
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
in_len = sizeof in_addr;
infd = accept (sfd, &in_addr, &in_len);
if (infd == -1)
{
if ((errno == EAGAIN) ||
(errno == EWOULDBLOCK))
{
/* We have processed all incoming
connections. */
break;
}
else
{
perror ("accept");
break;
}
}
//将地址转化为主机名或者服务名
s = getnameinfo (&in_addr, in_len,
hbuf, sizeof hbuf,
sbuf, sizeof sbuf,
NI_NUMERICHOST | NI_NUMERICSERV);//flag参数:以数字名返回
//主机地址和服务地址
if (s == 0)
{
printf("Accepted connection on descriptor %d "
"(host=%s, port=%s)\n", infd, hbuf, sbuf);
}
/* Make the incoming socket non-blocking and add it to the
list of fds to monitor. */
s = make_socket_non_blocking (infd);
if (s == -1)
abort ();
event.data.fd = infd;
event.events = EPOLLIN | EPOLLET;
s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
if (s == -1)
{
perror ("epoll_ctl");
abort ();
}
}
continue;
}
else
{
/* We have data on the fd waiting to be read. Read and
display it. We must read whatever data is available
completely, as we are running in edge-triggered mode
and won't get a notification again for the same
data. */
int done = 0;
while (1)
{
ssize_t count;
char buf[512];
count = read (events[i].data.fd, buf, sizeof(buf));
if (count == -1)
{
/* If errno == EAGAIN, that means we have read all
data. So go back to the main loop. */
if (errno != EAGAIN)
{
perror ("read");
done = 1;
}
break;
}
else if (count == 0)
{
/* End of file. The remote has closed the
connection. */
done = 1;
break;
}
/* Write the buffer to standard output */
s = write (1, buf, count);
if (s == -1)
{
perror ("write");
abort ();
}
}
if (done)
{
printf ("Closed connection on descriptor %d\n",
events[i].data.fd);
/* Closing the descriptor will make epoll remove it
from the set of descriptors which are monitored. */
close (events[i].data.fd);
}
}
}
}
free (events);
close (sfd);
return EXIT_SUCCESS;
}