Bash에서 두 개의 fifo 읽기

Bash에서 두 개의 fifo 읽기

두 개의 fifo에서 읽으려고 합니다(다른 fifo에 내용이 없으면 하나의 fifo에서 읽고 둘 다에 내용이 없으면 나중에 다시 시도하십시오). 프로세스가 계속 차단됩니다(시간 초과 옵션이 있어도).

나는 파일 읽기에 관한 몇 가지 다른 질문을 따랐습니다.while 루프를 사용하여 두 개의 입력 파일을 읽는 방법, 그리고루프 동안 IFS로 두 파일 읽기) 이제 다음 코드가 있습니다.

while true; do
    while
            IFS= read -r msg; statusA=$?
            IFS= read -u 3 -r socket; statusB=$?
            [ $statusA -eq 0 ] || [ $statusB -eq 0 ]; do
        if [ ! -z "$msg" ]; then
            echo "> $msg"
        fi
        if [ ! -z "$socket" ]; then
            echo ">> $socket"
        fi
    done <"$msg_fifo" 3<"$socket_fifo"
done

내가 뭐 잘못 했어요? 또한 paste/ 파이프를 사용할 수 없거나 cat프로세스가 완전히 차단됩니다.

답변1

이것은 기본 설계이며 이를 사용할 수 있는지 잘 모르겠습니다 (select() 시스템 호출 모듈이 있음)를 사용하여 이 문제 bash를 해결할 수 있지만 zsh현재 쉘이 해당 작업에 적합한 언어가 아닐 수도 있습니다. .

문제는 유닉스의 모든 것과 마찬가지로 간단하지만 실제 이해와 해결에는 더 깊은 사고와 실용적인 지식이 필요합니다.

그 이유는 VFS 객체를 열 때 기본적으로 시스템 커널에 의해 수행되는 파일 설명자에 차단 플래그가 설정되어 있기 때문입니다.

유닉스에서는 프로세스 협력이 IO 경계의 차단을 통해 동기화됩니다. 이는 매우 직관적이고 모든 것을 매우 우아하고 단순하게 만들고 순진한 간단한 프로그래밍을 중급 및 중급 응용 프로그램 프로그래머에게 "그냥 작동"하게 만듭니다.

문제의 개체(파일, fifo 등)를 열 때 해당 개체에 설정된 차단 플래그는 읽을 데이터가 없을 때 설명자에서 읽는 모든 내용이 즉시 전체 프로세스를 차단하도록 보장합니다. 이 블록은 일부 데이터가 반대쪽에서 개체에 채워지는 경우에만 차단 해제됩니다(파이프의 경우).

일반 파일은 적어도 파이프에 비해 "예외"입니다. 왜냐하면 IO 하위 시스템의 관점에서 볼 때 "절대 차단"하지 않기 때문입니다(실제로 차단하더라도 - 즉, 파일 fd에서 차단 플래그를 차단 해제해도 효과가 없습니다. 프로세스가 차단하기 때문입니다. 커널의 스토리지 fd 읽기 루틴에서 더 깊은 곳에서 발생합니다). 프로세스 POV의 디스크 읽기는 항상 즉각적이고 제로 타임이며 차단되지 않습니다(해당 읽기 중에 시스템 시계가 실제로 앞으로 점프하더라도).

이 디자인의 두 가지 효과를 관찰합니다.

첫째, 셸에서 일반 파일을 처리할 때는 IO 차단의 효과를 실제로 관찰할 수 없습니다(위에서 설명한 것처럼 파일은 실제로 차단되지 않기 때문입니다).

둘째, 여기에서 볼 수 있듯이 셸의 파이프에서 read()를 차단하면 프로세스는 기본적으로 영원히 차단됩니다. 차단은 적어도 나중에까지 많은 데이터가 채워지지 않기 때문입니다. 파이프 반대편에서 이 쪽이에요. 이 상태에서는 프로세스가 실행되지도 않고 CPU 시간도 소모하지 않으며, 커널은 더 많은 데이터가 도착할 때까지 외부에서 프로세스를 차단하므로 프로세스 시간 초과 루틴도 실행될 수 없습니다. 즉, 프로세스가 CPU 시간을 소모해야 하기 때문입니다. ) .

최소한 읽을 수 있는 충분한 데이터로 파이프를 채울 때까지 프로세스는 차단된 상태로 유지되며, 모든 데이터가 다시 소비되어 프로세스가 다시 차단될 때까지 잠시 차단이 해제됩니다.

생각해 보면 이것이 실제로 쉘의 파이프가 작동하는 이유입니다.

복잡한 쉘 파이프라인이 빠르거나 느린 프로그램에 어떻게 적응하는지 궁금한 적이 있습니까? 이것이 작동하도록 하는 메커니즘입니다. 출력을 빠르게 내보내는 빠른 생성기는 파이프라인의 다음 프로그램을 더 빠르게 읽게 하고, 파이프라인의 느린 데이터 생성기는 파이프라인의 후속 프로그램 읽기/실행을 느리게 만듭니다. 모든 것이 데이터 차단의 영향을 받습니다. 파이프 전체를 동기화하는 것은 마술처럼 작동합니다.

편집 : 추가 설명

문제에서 벗어나는 방법?

쉬운 방법은 없습니다. 내가 아는 한 bash에는 없습니다.

가장 쉬운 방법은 문제에 대해 더 많이 생각하고 다른 방식으로 재구성하는 것입니다.

차단의 특성이 위에서 설명되었으므로 쉘 프로그램에 의해 부과된 설계 제약을 이해하는 것이 가장 쉽습니다. 즉, 주 입력 스트림은 하나만 있습니다.

이렇게 하면 파일 입력(문제 없음)과 파이프를 처리할 수 있을 만큼 쉘이 강력해집니다.

여러 파이프(예: 두 개라도)에서 읽으면 모두 데이터가 있을 때까지 프로그램이 자연스럽게 차단되므로 두 파이프가 항상 데이터로 가득 차 있는지 확인할 수 있으면 이 방법이 작동합니다. 불행하게도 이것은 거의 작동하지 않습니다. 파이프에서 읽을 때 서로 얽히고 인터리빙되면 파이프가 무작위 순서로 채워지는 문제에 직면하게 됩니다. 특히 첫 번째 파이프가 비어 있으면 읽기가 의존하는 경우 전체 프로세스가 차단됩니다. 우리는 이 상황을 교착상태라고 부릅니다.

문제가 되는 파일 설명자에서 차단 플래그를 제거하여 여러 파이프에서 읽는 문제를 해결할 수 있지만 이제는 이를 처리하기 위해 적절하게 장착된 언어가 필요한 IO 스케줄링 및 데이터 멀티플렉싱 문제가 있습니다.

유감스럽게도 bash는 아직 충분히 갖춰져 있지 않습니다. 설사 그렇더라도 이제 이 기능이 어떻게 작동하는지 더 많이 이해해야 합니다.

답변2

@etosan과 @ilkkachu 간의 대화를 보았고 이를 사용하겠다는 귀하의 제안을 테스트한 결과 exec fd<>fifo이제 작동합니다. @etosan이 말했듯이 이것이 어떤 종류의 문제와 관련되어 있는지 확실하지 않지만 적어도 지금은 작동합니다.

exec 3<>"$socket_fifo" # to not block the read
while true; do
    while
            IFS= read -t 0.1 -r msg; statusA=$?
            IFS= read -t 0.1 -u 3 -r socket; statusB=$?
            [ $statusA -eq 0 ] || [ $statusB -eq 0 ]; do
        if [ ! -z "$msg" ]; then
            echo "> $msg"
        fi
        if [ ! -z "$socket" ]; then
            echo ">> $socket"
        fi
    done <"$msg_fifo"
done

이 상황에서 Bash를 사용하는 것에 대한 경고도 고려할 것입니다.

관련 정보