为什么会有EINTR错误,什么是慢系统调用:
下面这段话我是从网上找的,总结的挺好,我就直接拿来用:
- 慢系统调用:适用于那些可能永远阻塞的系统调用。永远阻塞的系统调用是指调用有可能永远无法返回,多数网络支持函数都属于这一类;
- 常见慢系统调用: 例如:accept、read、write、select、和open之类的函数来说,是可以进行重启的,函数出错为Interrupted system call,包括设置等待时间为永久阻塞等待的select、poll、epoll_wait也是一样的道理;
- EINTR错误的产生:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。例如:在socket服务器端,设置了信号捕获机制,有子进程,当在父进程阻塞于慢系统调用时由父进程捕获到了一个有效信号时,内核会致使accept返回一个EINTR错误(被中断的系统调用);
- 当碰到EINTR错误的时候,可以采取有一些可以重启的系统调用要进行重启(重新调用),而对于有一些系统调用是不能够重启的;不过对于套接字编程中的connect函数我们是不能重启的,若connect函数返回一个EINTR错误的时候,我们不能再次调用它,否则将立即返回一个错误。针对connect不能重启的处理方法是,必须调用select来等待连接完成。
如何模拟慢系统调用被信号中断?
这段代码着重看epoll_wait()前后,如何模拟系统被系统调用打断的场景:
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/epoll.h>
#include <assert.h>
#include <pthread.h>
#include <signal.h>
void sig_alrm(int signo) { return; }
struct fds {
int epollfd;
int sockfd;
};
#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10
int setnonblocking(int fd) {
int old_option = fcntl(fd, F_GETFD);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
void addfd(int epollfd, int fd, bool oneshot) {
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET; //给fd注册EPOLLIN和EPOLLET
if (oneshot) {
event.events |= EPOLLONESHOT; //给fd上注册EPOLLONESHOT事件
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking(fd);
}
//重置fd上的事件,
void reset_oneshot(int epollfd, int fd) {
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}
//工作线程
void* worker(void* arg) {
int sockfd = ((fds*)arg)->sockfd;
int epollfd = ((fds*)arg)->epollfd;
printf("This is a new work thread to recive data on fd,id = %u\n", pthread_self());
char buf[BUFFER_SIZE];
memset(buf, 0, BUFFER_SIZE);
while (1) {
int ret = recv(sockfd, buf, BUFFER_SIZE - 1, 0);
if (ret == -1 && errno == EAGAIN) {
reset_oneshot(epollfd, sockfd);
printf("read later \n");
break;
}
else if (ret == 0) {
close(sockfd);
printf("connection was closed\n");
break;
}
else {
printf("get data :%s\n", buf);
//模拟 该数据处理过程,假定使用了5秒
sleep(5);
}
}
//printf();
}
const char* ip = "127.0.0.1";
int port = 6600;
int main() {
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(listenfd != -1);
ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
if (ret == -1) {
perror("bind failed");
exit(-1);
}
ret = listen(listenfd, 5);
if (ret == -1) {
perror("listen failed");
exit(-1);
}
epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
assert(epollfd != -1);
addfd(epollfd, listenfd, false);
while (1) {
/*
timeout == -1,表示将永远阻塞
如果此时没有客户去连接这个fd,那么epoll_wait将一直阻塞
我们将在这里模拟如何用信号中断这样的阻塞,并且打印出对应的errno
*/
signal(SIGALRM,sig_alrm);
alarm(2);
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if (ret < 0) {
perror("epoll wait failed");
break;
}
for (int i = 0; i < ret; i++) {
int sockfd = events[i].data.fd;
if (sockfd == listenfd) {
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
int connectfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);
addfd(epollfd, connectfd, true);//对connfd开启ET模式
}
else if (events[i].events & EPOLLIN) {
pthread_t thread;
fds fds_for_new_worker;
fds_for_new_worker.epollfd = epollfd;
fds_for_new_worker.sockfd = sockfd;
//pthread_create(&thread, NULL, worker, (void*)&fds_for_new_worker);
}
else {
printf("something else happend \n");
}
}
}
close(listenfd);
return 0;
}
输出:
epoll wait failed: Interrupted system call