LINUX.ORG.RU

[Q] TCP/IP as an internal application IPC


0

0

Приложение состоит из ядра и некоторого числа независимых модулей (их число может быть увеличено), которые работают с различными источниками сообщений: железо, сеть, пользовательский ввод, и т.д. Все это собирается в _один_бинарник_. В ядро встроен скриптовый язык (например Tcl), где и будет реализована логика работы ядра.

Хотелось бы использовать замечательный Tcl'ный fileeevent и организовать/сериализовать доставку/отправку сообщений из/в модули используя TCP/IP, как средство внутреннего IPC, не изобретая бажных велосипедов.

Стоит ли с этим заморачиваться, когда количество коротких (макс. 4кб) сообшений может достигать пика в 2000/сек?

anonymous

>Стоит ли с этим заморачиваться, когда количество коротких (макс. 4кб) сообшений может достигать пика в 2000/сек?

с чем заморачиваться-то? напиши тестовую связку модулей, сэмулируй 2000 msg/s и посмотри на чём будет проседать

jtootf ★★★★★
()

> Все это собирается в _один_бинарник_.

_Ну и что это нам говорит?_

Это будет многопоточное приложение, или оно себя форкать отдельными процессами будет или это вообще однопоточное приложение?

> TCP/IP, как средство внутреннего IPC, не изобретая бажных велосипедов

Разделяемая память, мьютексы, семафоры, condition variables - чем они хуже?

anonymous
()

> Все это собирается в _один_бинарник_. ... используя TCP/IP, как средство внутреннего IPC, не изобретая бажных велосипедов. ... количество коротких (макс. 4кб) сообшений может достигать пика в 2000/сек...

:-)

Посмеялся, спасибо!

Die-Hard ★★★★★
()
Ответ на: комментарий от Die-Hard

> Посмеялся, спасибо!

Хочешь, палец покажу? Ещё посмеёшься.

anonymous
()
Ответ на: комментарий от anonymous

> Это будет многопоточное приложение, или оно себя форкать отдельными процессами будет или это вообще однопоточное приложение?

каждый модуль работает в своем треде со своим event-loop.

> Разделяемая память, мьютексы, семафоры, condition variables - чем они хуже?

нужны сессии, поэтому TCP/IP.

забыл написать, что ядро - бинарник, а модули подгружаются как плагины.

anonymous
()
Ответ на: комментарий от jtootf

> с чем заморачиваться-то? напиши тестовую связку модулей, сэмулируй 2000 msg/s и посмотри на чём будет проседать

да, сегодня потестирую. :)

anonymous
()
Ответ на: комментарий от anonymous

> каждый модуль работает в своем треде со своим event-loop.

Значит существует несколько параллельных потоков, но память у них общая.

> нужны сессии, поэтому TCP/IP.

Сессии - понятие более высокого уровня, некий контекст, сохраняемый между обменами сообщениями. В этом плане неважно как вы будете идентифицировать контексты - по сокету, идентификатору потока или ещё какму-то уникальному ИДу. TCP/IP будет полезен если есть вероятность, что функцонал будет разноситься по разным машинам, иначе это дороговатая разновидность IPC.

> забыл написать, что ядро - бинарник, а модули подгружаются как плагины.

А это как раз без разницы - ваше приложение должно предоставить модулям API, а уж как он будет сообщаться с ядром - вопрос лишь вашей фантазии.

anonymous
()
Ответ на: комментарий от anonymous

>> Посмеялся, спасибо!

>а в каком месте смешно то?

я приведу "теги" для смеха.

1. 4 кбайта, короткое сообщение, TCP :)

2. 2000/сек, TCP as an IPC (в рамках одной машины)

вобщем как-то сильно смешно все это выглядит.

Deleted
()

если у тебя есть tcl, ты можешь и comm из tcllib использовать. И тут во-первых не надо париться с сериализацией, а во-вторых оно умеет и tcp.

gaa ★★
()

> Стоит ли с этим заморачиваться, когда количество коротких (макс. 4кб) сообшений может достигать пика в 2000/сек?

2000 сообщений в секунду - это, как минимум, 2000 системных вызовов write, 2000 read и ещё, вероятно, 2000 select. Переход из юзерспейса в ядро и обратно - очень дорогая операция. tcp-стек - очень навороченная и ресурсоёмкая штука.

Правильный ответ уже написали: shared memory, условные переменные, барьеры, мьютексы.

mv ★★★★★
()

если у тебя модули so-шками подключаются и всё в одном адресном пространстве то использование TCP это ппц. Можно создать очередь сообщений связанным списком или воспользоваться готовой либой, наверняка есть такие. Или через callback-функции всё сделать(так работает pam, если не ошибаюсь).Регистрацию событий возложить на ядро. Или делать polling модулей через определённые промежутки времени, но это не тру :).

Если используется tcl то, я так понимаю, особо быстро это и не должно работать.Тогда зачем в один бинарник? Пусть ядро сидит и unix-сокет слущает а к нему клиенты подключаются, авторизовываются и шлют запросы. Тогда, если один из клиентов заглючит или отвалится, всё остальное продолжит работать. Архитектура "микроядра" :). Плюс отлаживать удобнее.

true_admin ★★★★★
()
Ответ на: комментарий от mv

> Переход из юзерспейса в ядро и обратно - очень дорогая операция.

дорогая, но процы щас мощные, вижу на одном сервере по vmstat -s 10тыщ в секунду CPU context switch, при этом сервер ещё и клиентов успевает обслужить и cpu idle 50-60% :). Один проц вполне 30к ctx switch держит, хотя, конечно, это не самый лучший способ потратить процовые ресурсы.

> MTU для lo 16к, влезет :)

оно и так влезет, тока будет разбито на куски не больше mtu, лишь бы recvbuf-а хватило, через setsockopt можно попытаться увеличить размеры.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

> Если используется tcl то, я так понимаю, особо быстро это и не должно работать.

Какие-то у тебя странные представления об использовании связующих языков.

gaa ★★
()
Ответ на: комментарий от mv

> Переход из юзерспейса в ядро и обратно - очень дорогая операция.

Цифры? А то Линус любил говорить, что системные вызовы в Линуксе дешевы.

> tcp-стек - очень навороченная и ресурсоёмкая штука.

Цифры, цифры?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> Цифры? А то Линус любил говорить, что системные вызовы в Линуксе дешевы.

Это не от линукса а от архитектуры аппаратного обеспечения зависит. В той доке которую я читал там дофига надо сделать для переключения контекста. И, моя догадка, это собьёт кэши проца т.к. в них будут загружены инструкции ядра. Впрочем, тут вот в доке http://www.linfo.org/context_switch.html рассказывают что mode switch и context switch разные вещи. Возможно, mode switch не такая уж и страшная штука.

> Цифры, цифры?

это очевидно. Несколько уровней абстракции, подсчёт контрольных сумм, распиливание пакетов по MTU, данные храняться до тех пор пока ack не будет получен, обработка icmp, пакеты отдельно "проходят" фаервол, NAT, по таблице маршрутизации ищется куда его закинуть(это может кэшироваться), conntrack за ними смотрит, соединение не сразу закрывается а висит ещё в TIME_WAIT(во фре можно для локальных соединений настроить чтобы они сразу схлопывались) итд итп. Локальные ipc значительно проще и (подозреваю)производительнее.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

>> Цифры, цифры?

> это очевидно.

Ну посчитай в уме, если можешь. Если mv вернется с результатами SystemTap, сравним.

> Несколько уровней абстракции,

Ага, ты сверху излагал про переключение контекста, которое свелось к переключению режима.

> подсчёт контрольных сумм

Копейки

> распиливание пакетов по MTU

Да уж, затратная операция. Особенно при MTU как у lo.

> данные храняться до тех пор пока ack не будет получен

Прозреваю оптимизации, да и в разделяемой памяти данные тоже хранятся "до ack".

> обработка icmp

Не уверен, что это актуально для lo.

Короче говоря, интересны экспериментальные цифры. Я припоминаю, что gettimeofday давало результаты с разницей в десятки мкс на старый процах и ядрах (тогда еще не было ни sysenter, ни vdso).

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> Цифры? А то Линус любил говорить, что системные вызовы в Линуксе дешевы.

У меня получается ~2600 тактов через int 0x80 (напрямую), ~1300 через sysenter (через libc'шный syscall(), тут ещё скинуть сколько-то надо). Плюс незамеряемые артефакты от инвалидации L1. Если постоянно так дёргать сисколлы, то L1 будет "холодным".

> Цифры, цифры?

Какие конкретно? Хотя, не буду делать глупые замеры. _Мне_ и так понятно, что tcp в сравнении queue через shmem будет очень медленно.

mv ★★★★★
()
Ответ на: комментарий от tailgunner

> Короче говоря, интересны экспериментальные цифры. Я припоминаю, что gettimeofday давало результаты с разницей в десятки мкс на старый процах и ядрах (тогда еще не было ни sysenter, ни vdso).

int уже тоже не такой парковочный тормоз, что в молодости на 80386.

mv ★★★★★
()
Ответ на: комментарий от tailgunner

> Ну посчитай в уме, если можешь. Если mv вернется с результатами SystemTap, сравним.

Ладно, чертяка, сейчас замерю :) Только systemtap там не в тему.

mv ★★★★★
()
Ответ на: комментарий от mv

> ~1300 через sysenter (через libc'шный syscall()

Если взять проц с частотой 1.3ГГц, получим 1/1.3e9*1.3e3 == 1/1e6, т.е. 1мкс на сисколл. Я бы не назвал такую операцию "очень медленной".

> _Мне_ и так понятно, что tcp в сравнении queue через shmem будет очень медленно.

Мне пока понятно, что будет медленнее, но непонятно, насколько.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Ну, значит те доки что я читал врали по поводу сисколов
или устарели. Вот такая прога говорил что пара миллионов
сисколов в секунду не проблема для средней тачки. Я мерял
вот такой самопальной прогой для грубой оценки:


#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <stdint.h>
#include <signal.h>

uint64_t cnt;
int fd; 


void sig_cb(int sig) {
    printf("cnt: %llu\n", cnt);
    close(fd);
    exit(0);
}

int main(void) {
    char buf[] = "test";

    cnt = 0;
    fd = open("/dev/null", O_WRONLY);
    assert (fd != -1);
    printf("fd=%d\n", fd);

    signal(SIGINT, sig_cb);

    for(;;) {
        //TODO: check write return value
        write(fd, buf, sizeof(buf));
        cnt++;
    }

};


Кто возмётся померять tcp? :).

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

но даже если tcp окажется не таким уж тормозом это не повод его юзать в локальном ipc.

true_admin ★★★★★
()
Ответ на: комментарий от tailgunner

> Если взять проц с частотой 1.3ГГц, получим 1/1.3e9*1.3e3 == 1/1e6, т.е. 1мкс на сисколл. Я бы не назвал такую операцию "очень медленной".

Основная проблема в том, что L1 при переключении контекста должен сбрасываться.

> Мне пока понятно, что будет медленнее, но непонятно, насколько.

При 200к пакетов по 4кб через lo получается около 29300 тактов на write().

mv ★★★★★
()
Ответ на: комментарий от tailgunner

> Тогда ой. Я думал, у тебя все инструменты готовы.

Я не совсем понимаю идею замеров системтапом. Что им такого замерять, что нельзя rdtsc прямо из тестовой программы замерить?

mv ★★★★★
()
Ответ на: комментарий от true_admin

> signal(SIGINT, sig_cb);

Сигналы в ядре тоже кучерявые. В ядро вообще при любой возможности лучше не надо лезть.

mv ★★★★★
()
Ответ на: комментарий от true_admin

> Кто возмётся померять tcp? :).

Как-то так...

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define _GNU_SOURCE
#define __USE_GNU
#include <pthread.h>
#include <sched.h>
#include <assert.h>

typedef struct ticks {
    unsigned long hi;
    unsigned long lo;
} ticks_t;

void inline rdtsc(ticks_t* t)
{
    asm volatile ("rdtsc\n"
              : "=d"(t->hi), "=a"(t->lo));
}

long ticks_diff(ticks_t* begin, ticks_t* end)
{
    return ((double)((unsigned long)-1 + 1) * (end->hi - begin->hi)
        + (end->lo - begin->lo));
}

#define N 200000
#define PORT 10001

void* foo(void *arg)
{
    int sock, err, i;
    struct sockaddr_in sa;
    char buf[4096];
    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    assert(sock > 0);
    sa.sin_addr.s_addr = 0x0100007f;
    sa.sin_family = AF_INET;
    sa.sin_port = htons(PORT);
    err = connect(sock, (struct sockaddr *)&sa, sizeof(sa));
    assert(err == 0);
    printf("client connected\n");
    for (i = 0; i < N; i++) {
        read(sock, buf, 4096);
    }
    close(sock);
    printf("client finished\n");
}

int main()
{
    ticks_t t1 = { 0 }, t2 = { 0 };
    double n = 0.0, diff;
    int i;
    int sock, err, client;
    struct sockaddr_in sa;
    socklen_t len;
    pthread_t t, self;
    cpu_set_t cpuset;
    char buf[4096];

    self = pthread_self();

    CPU_ZERO(&cpuset);
    CPU_SET(1, &cpuset);

    err = pthread_setaffinity_np(self, sizeof(cpuset), &cpuset);
    assert(err == 0);
    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    assert(sock > 0);
    memset(&sa, 0, sizeof(sa));
    sa.sin_addr.s_addr = htonl(INADDR_ANY);
    sa.sin_family = AF_INET;
    sa.sin_port = htons(PORT);
    err = bind(sock, (const struct sockaddr*)&sa, sizeof(sa));
    assert(err == 0);
    err = listen(sock, 10);
    assert(err == 0);
    printf("listen\n");
    err = pthread_create(&t, 0, foo, 0);
    assert(err == 0);
    client = accept(sock, (struct sockaddr *)&sa, &len);
    assert(client > 0);
    printf("got client\n");
    for (i = 0; i < N; i++) {
        rdtsc(&t1);
//        send(0, 0, 0, 0);
//        syscall(666);
        write(client, buf, sizeof(buf));
//        asm volatile ("mov $666999, %eax\n int $0x80\n");
        rdtsc(&t2);
        diff = ticks_diff(&t1, &t2);
        if (diff < 0.0) {
            --i;
        } else {
            n += diff / N;
        }
    }
    printf("%3.2f\n", n);
}

mv ★★★★★
()
Ответ на: комментарий от mv

> Основная проблема в том, что L1 при переключении контекста должен сбрасываться.

При переключении контекста или режима? Я вечно забываю, где в учебниках написано, какие кэши когда сбрасываются.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> При переключении контекста или режима? Я вечно забываю, где в учебниках написано, какие кэши когда сбрасываются.

Режим - это ring0,3, контекст - процесс (task)? При смене режима вообще всё сбрасывается, при смене task'а сбрасывается L1, т.к. он для скорости работает с уже оттранслированными адресами.

mv ★★★★★
()
Ответ на: комментарий от mv

> Я не совсем понимаю идею замеров системтапом. Что им такого замерять, что нельзя rdtsc прямо из тестовой программы замерить?

1) у меня наглости не хватает просить людей мерять с помощью rdtsc из-за спора на форуме; 2) интересны внутренние оверхэды TCP (IIUC, SystemTap умеет их мерять).

tailgunner ★★★★★
()
Ответ на: комментарий от mv

> Режим - это ring0,3, контекст - процесс (task)?

> При смене режима вообще всё сбрасывается, при смене task'а сбрасывается L1, т.к. он для скорости работает с уже оттранслированными адресами.

Хм, мне всегда казалось, что при смене режима не сбрасывается ничего (если в L1 хранятся физические адреса), а при смене контекста - наоборот, сбрасывается и TLB, и и все кэши до L3 включительно. Надо будет снова покурить управление памятью.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> интересны внутренние оверхэды TCP (IIUC, SystemTap умеет их мерять).

Может (это ведь в конечном виде обычный ядерный модуль), но он будет вносить ощутимые погрешности самим фактом своего существования. Однако, можно просто вычесть вызов пустого сисколла из времени write(), получится вполне достоверный оверхед на саму логику сисколла.

mv ★★★★★
()
Ответ на: комментарий от tailgunner

> Хм, мне всегда казалось, что при смене режима не сбрасывается ничего (если в L1 хранятся физические адреса), а при смене контекста - наоборот, сбрасывается и TLB, и и все кэши до L3 включительно. Надо будет снова покурить управление памятью.

Может быть, я не прав, надо тоже покурить.

mv ★★★★★
()

может лучше unix domain sockets?

imp ★★
()
Ответ на: комментарий от true_admin

> вижу на одном сервере по vmstat -s 10тыщ в секунду CPU context switch,

Ты уверен, что это _в секунду_ ? :)

anonymous
()
Ответ на: комментарий от mv

> Может быть, я не прав, надо тоже покурить.

Покурить про L1. Остальные кеши смысла сбрасывать нет.

mv ★★★★★
()
Ответ на: комментарий от mv

>> с чего вы взяли, что L1 вообще должен сбрасываться?

> Ульрих Дреппер сказал ;)

Ульриху можно верить :) А где он это сказал и чем обосновал?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> Ульриху можно верить :) А где он это сказал

На внутренней шлифовочной презентации своего манускрипта по компутерной памяти ;)

> и чем обосновал?

Тем, что каждый процесс живёт в своём адресном пространстве. Соответственно, если переключение происходит не на тред этого же процесса, то из соображений изоляции АП разных процессов L1 нужно сбрасывать. Вероятно, я слукавил про безоговорочную инвалидацию L1.

mv ★★★★★
()
Ответ на: комментарий от mv

> При 200к пакетов по 4кб через lo получается около 29300 тактов на write().

Разброс 26-32 т. в комбинациях "2 треда на одном ядре", "2 треда на двух ядрах", "2 процесса на одном ядре", "2 процесса на двух ядрах". Всё равно, даже 26000 тактов слишком дофига на отправку одного сообщения.

mv ★★★★★
()
Ответ на: комментарий от mv

> Соответственно, если переключение происходит не на тред этого же 
> процесса, то из соображений изоляции АП разных процессов L1 нужно 
> сбрасывать

а причем тут L1? ;)
TLB да, но не L1

rei3er
()
Ответ на: комментарий от rei3er

> а причем тут L1? ;) TLB да, но не L1

Ты меня озадачил часа на полтора ковыряния в ядре и интеловских даташитах. Действительно, зачем инвалидировать то, что измениться не может? В конце концов нашёл нужную главу (3.3.5) в дрепперовской рукописи, перечитал, и понял, что я всё перепутал. В L1 используются виртуальные адреса, поэтому он не может не инвалидироваться при смене АП.

mv ★★★★★
()
Ответ на: комментарий от mv

> В L1 используются виртуальные адреса, поэтому он не может не инвалидироваться при смене АП.

С другой стороны, это же значит, что при переключении режима L1 сбрасывать не нужно, потому что виртуальные адреса не меняются.

Впрочем, Ульрих пишет, что L1 маленький, так что без разницы :)

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> С другой стороны, это же значит, что при переключении режима L1 сбрасывать не нужно, потому что виртуальные адреса не меняются.

Почему тогда обычные программы в пространство ядра не могут писать? :)

mv ★★★★★
()
Ответ на: комментарий от anonymous

> Ты уверен, что это _в секунду_ ? :)

да, запускал как-то так: gcc ./test.c && a.out & sleep 10 && killall -INT a.out Полученный результат делил на 10. Можно было и в проге это сделать, но времени не было ковыряться. В любом случае ты можешь скомпилить и посмотреть.

> Почему тогда обычные программы в пространство ядра не могут писать? :)

MMU защищает память ядро от прог. Но ведь при переходе в kernel mode настройки MMU не меняются? Как был vm split настроен так он и остался, не? tlb сбрасываться при сисколе не должен, я где-то про это читал. Вот почему ядро не может писать в память программы я не понимаю.

> Сигналы в ядре тоже кучерявые. В ядро вообще при любой возможности лучше не надо лезть. Так тот код что ты выделил лезит в ядро всего два раза, разве нет? Один раз когда устанавливает хендлер для сигнала, второй раз когда этот сигнал генерируется.

> может лучше unix domain sockets? для передачи данных внутри одного процесса это слишком круто имхо :)

true_admin ★★★★★
()
Ответ на: комментарий от mv

А для чего сделано вот это? ((unsigned long)-1 + 1) И почему diff может быть меньше нуля если это время которое заняла операция? Ну и чтобы уменьшить ошибку округления лучше n делит на N в самом конце, не?

true_admin ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.