Межпроцессное взаимодействие через Pipes

Известно, что во время межпроцессного взаимодействия в Linux процессы взаимодействуют друг с другом через специальный файл с именем "Pipe".

Также известно, что операции, выполняемые над этим файлом, — это запись одним процессом и чтение одним процессом для связи друг с другом.

Теперь вопрос:

Выполняются ли эти операции записи и чтения параллельно во время связи (операции выполняются параллельно)? а если не чем,

Что происходит, когда один из процессов переходит в состояние SLEEP во время связи? Выполняет ли он сначала операцию записи, чтобы второй процесс читал, или сразу переходит в спящий режим, не выполняя никаких операций записи и ? операция чтения?


person HarshitMadhav    schedule 14.04.2017    source источник


Ответы (1)


Процесс-отправитель может записывать до тех пор, пока буфер канала не заполнится (64 КБ в Linux с версии 2.6.11). После этого write(2) заблокируется.

Процесс получения будет заблокирован до тех пор, пока данные не будут доступны для read(2).

Более подробно о буферизации каналов см. https://unix.stackexchange.com/a/11954.

Например, эта программа

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int
main(int argc, char *argv[])
{
    int pipefd[2];
    pid_t cpid;
    char wbuf[32768];
    char buf[16384];

    /* Initialize writer buffer with 012...89 sequence */
    for (int i = 0; i < sizeof(wbuf); i++)
      wbuf[i] = '0' + i % 10;

    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    cpid = fork();
    if (cpid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (cpid == 0) {    /* Child reads from pipe */
        close(pipefd[1]);          /* Close unused write end */
        while (read(pipefd[0], &buf, sizeof(buf)) > 0);
        close(pipefd[0]);
        _exit(EXIT_SUCCESS);

    } else {            /* Parent writes sequence to pipe */
        close(pipefd[0]);          /* Close unused read end */
        for (int i = 0; i < 5; i++)
          write(pipefd[1], wbuf, sizeof(wbuf));
        close(pipefd[1]);          /* Reader will see EOF */
        wait(NULL);                /* Wait for child */
        exit(EXIT_SUCCESS);
    }
}

будет производить следующую последовательность при запуске с gcc pipes.c && strace -e trace=open,close,read,write,pipe,clone -f ./a.out:

open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
close(3)                                = 0
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\3\2\0\0\0\0\0"..., 832) = 832
close(3)                                = 0
pipe([3, 4])                            = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f32117489d0) = 21114
close(3)                                = 0
write(4, "01234567890123456789012345678901"..., 32768) = 32768
write(4, "01234567890123456789012345678901"..., 32768) = 32768
write(4, "01234567890123456789012345678901"..., 32768strace: Process 21114 attached
 <unfinished ...>
[pid 21114] close(4)                    = 0
[pid 21114] read(3, "01234567890123456789012345678901"..., 16384) = 16384
[pid 21114] read(3,  <unfinished ...>
[pid 21113] <... write resumed> )       = 32768
[pid 21114] <... read resumed> "45678901234567890123456789012345"..., 16384) = 16384
[pid 21113] write(4, "01234567890123456789012345678901"..., 32768 <unfinished ...>
[pid 21114] read(3, "01234567890123456789012345678901"..., 16384) = 16384
[pid 21114] read(3,  <unfinished ...>
[pid 21113] <... write resumed> )       = 32768
[pid 21114] <... read resumed> "45678901234567890123456789012345"..., 16384) = 16384
[pid 21113] write(4, "01234567890123456789012345678901"..., 32768 <unfinished ...>
[pid 21114] read(3, "01234567890123456789012345678901"..., 16384) = 16384
[pid 21114] read(3,  <unfinished ...>
[pid 21113] <... write resumed> )       = 32768
[pid 21114] <... read resumed> "45678901234567890123456789012345"..., 16384) = 16384
[pid 21113] close(4)                    = 0
[pid 21114] read(3, "01234567890123456789012345678901"..., 16384) = 16384
[pid 21114] read(3, "45678901234567890123456789012345"..., 16384) = 16384
[pid 21114] read(3, "01234567890123456789012345678901"..., 16384) = 16384
[pid 21114] read(3, "45678901234567890123456789012345"..., 16384) = 16384
[pid 21114] read(3, "", 16384)          = 0
[pid 21114] close(3)                    = 0
[pid 21114] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21114, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

Вы заметите, что операции чтения и записи чередуются, а процессы записи и чтения несколько раз блокируются, если канал заполнен или для чтения недостаточно данных.

person Gerd Flaig    schedule 14.04.2017
comment
Эти операции происходят параллельно? - person HarshitMadhav; 14.04.2017
comment
Операции чтения и записи в каналах могут быть параллельными, однако read(2) будет возвращать только байты для завершенных операций записи. Учитывая, что записи менее чем PIPE_BUF байтов являются атомарными, считыватель будет возвращать байты только после завершения записи. См. трубу (7) для получения дополнительной информации. - person Gerd Flaig; 14.04.2017
comment
Не могли бы вы объяснить более ясно? Я думаю, это не то, что я спросил. - person HarshitMadhav; 14.04.2017
comment
pipe_read и pipe_write оба получают мьютекс на канале, поэтому не могут работать параллельно. См. lxr.free-electrons.com/source/fs/pipe.c #L250 и lxr.free-electrons.com/source /fs/pipe.c#L357. - person Gerd Flaig; 14.04.2017
comment
Если вы не используете ядро ​​с CONFIG_PREEMPT=y, вызов записи не может быть вытеснен до тех пор, пока он не будет выполнен (в случае, если в буфере достаточно места) или заблокирован (что заставит процесс заснуть). Если вам нужны более конкретные ответы, подумайте о том, чтобы обновить свой вопрос диаграммой временной последовательности для каждого сценария с определенным порядком событий. - person Gerd Flaig; 15.04.2017
comment
Я добавил пример кода с выводом трассировки системного вызова для пояснения. - person Gerd Flaig; 15.04.2017
comment
Итак, вы имеете в виду, что во время межпроцессного взаимодействия, если один процесс переходит в спящий режим, тогда связь между ними через канал останавливается и возобновляется, когда другой процесс просыпается? - person HarshitMadhav; 17.04.2017