#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[]){
if(argc < 2){
puts("err argv");
return -1;
}
int r_fd = open(argv[1], O_RDWR);
int w_fd = open(argv[1], O_RDWR);
fd_set r_set;
fd_set w_set;
char w_buf[4096];
char r_buf[2048];
int read_count = 0,write_count = 0;
while(1){
FD_ZERO(&r_set);
FD_ZERO(&w_set);
FD_SET(r_fd, &r_set);
FD_SET(w_fd, &w_set);
int ret = select(w_fd + 1, &r_set, &w_set, NULL, NULL);
if(FD_ISSET(r_fd, &r_set)){
read(r_fd, r_buf, sizeof(r_buf));
printf("read count:%d\n", read_count++);
}
if(FD_ISSET(w_fd, &w_set)){
write(w_fd, w_buf, sizeof(w_buf));
printf("write count:%d\n", write_count++);
}
//sleep(1);
}
return 0;
}
실행 코드: mkfifo 1.fifo && gcc main.c -o main && ./main 1.fifo
답변1
if(FD_ISSET(w_fd, &w_set)){
write(w_fd, w_buf, sizeof(w_buf));
printf("write count:%d\n", write_count++);
}
select
fd가 쓰기 가능하다는 말을 듣는 다면 , 이는 다음과 같이 쓸 수 있다는 의미입니다.최소 1바이트. FIFO 버퍼가 관리되는 방식에 따라 임계값이 1보다 높을 수 있지만 이것이 차단 없이 4096바이트를 쓸 수 있다는 의미는 아닙니다.
이 코드는 정적으로 잘못되었습니다. 합리적으로 견고하려면 두 fd 모두 비차단 모드로 설정되어야 합니다(그리고 적절하게 코드를 처리 EAGAIN
/ 반환하도록 준비해야 합니다).EWOULDBLOCK
이것이유w_fd
읽기 버퍼 크기로 인해 노출되는 문제 중 일부는 FIFO가 충분히 비어 있지만(쓰기 가능한 것처럼 보이도록) 쓰기를 완료하기에는 너무 꽉 차서(따라서 블록) 상황에 도달할 수 있는지 여부와 관련이 있습니다.
차단된 프로세스에 디버거를 연결하거나 strace에서 실행하여 이를 확인하거나 위조할 수 있습니다. 이것들은 사용법을 배워야 하는 완전히 일반적인 도구입니다.
답변2
문제는 단일 선택 후 r_set 및 w_set을 확인할 때 발생하는 것 같습니다. 읽기 또는 쓰기에 대한 경쟁 조건으로 인해 결과가 무효화될 수 있습니다.
이는 추가 디버깅 및 일부 결과가 포함된 강력한 버전입니다.
쓰기 전에 사용 가능한 읽기를 수행하는 것이 중요합니다. 쓰기 우선순위를 부여하면 읽기 전에 fifo(최대 64KB)가 채워집니다. 이는 원본 코드의 경쟁 조건의 일부일 수 있습니다.
// xSelect.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[]){
if(argc < 2){
puts("err argv");
return -1;
}
ssize_t r_res, w_res;
int r_fd, w_fd;
r_fd = open(argv[1], O_RDWR);
w_fd = open(argv[1], O_RDWR);
fprintf (stderr, "r_fd %d, w_fd %d\n", r_fd, w_fd);
fd_set r_set;
fd_set w_set;
char w_buf[4096];
char r_buf[1235];
for (int j = 0, w = 0; j < 20; j++) {
FD_ZERO(&r_set);
FD_ZERO(&w_set);
FD_SET(r_fd, &r_set);
FD_SET(w_fd, &w_set);
int ret = select(w_fd + 1, &r_set, &w_set, NULL, NULL);
if(FD_ISSET(r_fd, &r_set)){
r_res = read(r_fd, r_buf, sizeof(r_buf));
fprintf (stderr, "Read %zd\n", r_res);
} else if(w++ < 3 && FD_ISSET(w_fd, &w_set)){
w_res = write(w_fd, w_buf, sizeof(w_buf));
fprintf (stderr, "Write %zd\n", w_res);
} else {
fprintf (stderr, "Tick\n");
}
}
return 0;
}
$ make xSelect && echo Run && ./xSelect xSelect.fifo
cc xSelect.c -o xSelect
Run
r_fd 3, w_fd 4
Write 4096
Read 1235
Read 1235
Read 1235
Read 391
Write 4096
Read 1235
Read 1235
Read 1235
Read 391
Write 4096
Read 1235
Read 1235
Read 1235
Read 391
Tick
Tick
Tick
Tick
Tick
$
편집: 나는 이것을 지나치게 생각하고 있을 수도 있습니다. 나는 r_set과 w_set가 단일 읽기 및/또는 쓰기에 직접적으로 영향을 받지 않는다고 생각합니다. 그러나 이러한 작업에 의해 제거됩니다.
원본 코드에서는 w_fd
FIFO가 완전히 가득 찰 때까지 항상 쓰기가 가능합니다. 따라서 동일한 수의 긴 쓰기와 짧은 읽기를 수행하면 작성자가 결국 중단됩니다. 짧은 읽기가 2048바이트인 경우 fifo를 채우는 데 약 32사이클이 필요합니다. 짧은 읽기가 48바이트인 경우 fifo를 채우는 데 17사이클이 필요합니다.
분명히 장기적으로 평균 쓰기 속도와 읽기 속도는 같아야 합니다. 일반적으로 커널은 작성기(다른 프로세스에 있음)를 예약하지 않음으로써 이를 수행하지만 코드는 읽기 1회, 쓰기 1회를 강제합니다. 그래서 w_set.w_fd
결국 불안정해집니다.
한 가지 문제는 select()가 쓰려는 데이터의 양을 모른다는 것입니다. fifo에 1바이트의 공간이 있으면 w_fd는 쓰기 가능합니다. 쓰기가 잘렸는지, 차단되었는지, 실패했는지, 동일한 fifo를 사용하는 두 fd 사이에 차이가 있는지는 알 수 없습니다. 대답은 OS에 따라 달라질 수 있고 FIFO나 소켓을 사용하는지 여부 또는 기타 요인에 따라 달라질 수 있으므로 테스트할 가치도 없습니다. 부분 쓰기를 처리하고 EAGAIN 오류를 반환하도록 코드를 작성해야 합니다.그리고차단하다.
귀하의 코드는 fifo를 완전히 채우는 데 어려움을 겪는 반면, 내 코드는 모든 쓰기 후에 fifo를 불필요하게 완전히 비우는 데 어려움을 겪습니다. 적절한 데모는 가능성을 탐색하기 위해 동작을 조정(또는 무작위화)해야 합니다.