Terminal I / O (как эмулировать чтение / запись терминала с определенной областью памяти?) - PullRequest
1 голос
/ 28 декабря 2010

Я работаю на платформе Embedded, где у меня есть полный доступ к физической памяти для прямого чтения / записи.

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

Я хочу отобразить сообщения из приложения RT на консоль Linux (и, возможно, отправить команду из Linux в приложение RT).Мое текущее решение - вывести все из приложения RT в последовательный порт.Затем в Linux я буду читать ввод последовательного порта.

Это работает, но кажется ненужным, потому что приложения RT и Linux находятся на одной физической машине.Вспоминая, как работает последовательный порт, он имеет буфер памяти, и приложение может читать / записывать в этот буфер.Таким образом, мне было интересно, возможно ли подключить дисплей терминала к определенной области памяти (т. Е. 0x10000), и когда приложение RT «напечатает» какое-либо сообщение на 0x10000, терминал Linux отобразит сообщение?

Ответы [ 3 ]

1 голос
/ 29 декабря 2010

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

EDIT:

Как уже упоминалось в комментариях, тот факт, что вы выполняете процесс в реальном времени, обескураживает, и нативный IPC, вероятно, не лучший выбор. Вот статья, которую я только что прогуглил, которая, кажется, дает ответ, который вы ищете.

Если вы не хотите читать все это, он предлагает либо FIFO, либо Shared Memory в качестве примитива параллелизма, который вы должны использовать, в зависимости от того, какой тип связи вам требуется. Исходя из личного опыта, FIFO в долгосрочной перспективе приводит к уменьшению головной боли, потому что вам приходится меньше беспокоиться о синхронизации.

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

1 голос
/ 29 декабря 2010

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

Зарезервируйте кусок памяти во время инициализации ядра. Скажем, 4 КБ, поскольку это часто страница, но 256 или даже 16 байтов тоже подойдут. Зарезервируйте секунду для другого направления.

Когда «писатель» канала хочет что-то сказать, он сначала проверяет, является ли первое 32-битное слово нулевым. Если это так, то, начиная с 5-го байта, он записывает сообщение - текстовые или двоичные данные, что угодно, максимум до 4k - 4 = 4092 байта. Затем оно устанавливает первое слово равным количеству написанных им байтов.

Приемник отслеживает количество байтов в первом слове канала, который он принимает. Когда он видит ненулевой счетчик байтов, он читает столько байтов из памяти. Затем он устанавливает счетчик байтов равным нулю, чтобы показать автору записи, что теперь новое сообщение может быть написано по своему усмотрению.

Единственное, от чего это зависит, это то, что вы фактически получаете доступ к реальной памяти или работаете через один и тот же кеш, и что у вас есть атомарная операция записи для записи количества байтов (если у вас нет 32-битной атомарной записи, используйте 16-битный счетчик, что в любом случае достаточно, или уменьшите буфер и используйте 8-битный счетчик). Поскольку записывающее устройство может установить ненулевое значение только в том случае, если оно равно нулю, а считывающее устройство может установить нулевое значение только в том случае, если оно ненулевое, все работает.

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

О, прежде чем приступить к выполнению кода и выполнить поиск в Интернете. Я уверен, что уже есть какой-то подобный механизм или что-то еще, доступное для соединения ваших компонентов RT и linux. Но научиться делать это самостоятельно тоже может быть интересно - и необходимо, если вы столкнетесь с такой проблемой на небольшой встроенной системе без ОС, которая предоставляет вам эту функциональность.

0 голосов
/ 30 декабря 2010

Я успешно использовал систему fifo с общей памятью для связи между процессами (хотя и не в том же сценарии, что и у вас).Суть в том, что только один поток может быть производителем, а один поток может быть потребителем.Вам также необходимо убедиться, что, как отметил Крис Страттон, любое кэширование правильно обрабатывается с использованием соответствующих барьеров памяти.У Linux есть довольно простой API для барьеров памяти, я не знаю, что могло бы быть доступно вашему приложению в реальном времени.Я попытался определить, где могут потребоваться барьеры памяти.

Ниже приводится непроверенная (и полностью неоптимизированная) реализация разделяемого fifo.Ваше приложение RT может записывать символы в fifo, а приложение или драйвер Linux может считывать символы из fifo.В идеале у вас должен быть механизм для стороны Linux, чтобы сигнализировать, что данные готовы (возможно, в противном случае неиспользуемый GPIO, который может инициировать прерывание, когда сторона RT его нажимает?).В противном случае сторона Linux может опрашивать данные в fifo, но это, вероятно, далеко не идеально по обычным причинам.

struct fifo {
    char volatile* buf;
    int buf_len;
    int volatile head;  // index to first char in the fifo
    int volatile tail;  // index to next empty slot in fifo
                         // if (head == tail) then fifo is empty
                         // if (tail < head) the fifo has 'wrapped'
};

void fifo_init( struct fifo* pFifo, char* buf, int buf_len)
{
    pFifo->buf = buf;
    pFifo->buf_len = buf_len;
    pFifo->head = 0;
    pFifo->tail = 0;
}

int fifo_is_full( struct fifo* pFifo)
{
    int head;
    int tail;

    // a read barrier may be required here
    head = pFifo->head;
    tail = pFifo->tail;

    // fifo is full if ading another char would cause
    //    tail == head
    ++tail;
    if (tail == pFifo->buf_len) {
        tail = 0;
    }

    return (tail == head);
}


int  fifo_is_empty(  struct fifo* pFifo)
{
    int head;
    int tail;

    // a read barrier may be required here
    head = pFifo->head;
    tail = pFifo->tail;

    return head == tail;
}


// this function is the only one that modifies
// the pFifo->tail index.  It can only be used
// by a single writer thread.
int fifo_putchar( struct fifo* pFifo, char c)
{
    int tail = pFifo->tail;

    if (fifo_is_full(pFifo)) return 0;

    pFifo->buf[tail] = c;
    ++tail;
    if (tail == pFifo->buf_len) {
        tail = 0;
    }

    //note: the newly placed character isn't actually 'in' the fifo
    //  as far as the reader thread is concerned until the following
    //  statement is run    
    pFifo->tail = tail;

    // a write barrier may need to be placed here depending on 
    // the system.  Microsoft compilers place a barrier by virtue of
    // the volatile keyword, on a Linux system a `wmb()` may be needed
    // other systems will have other requirements
    return 1;
}


// this function is the only one that modified the
// pFifo->head index.  It can only be used by a single
// reader thread.
int fifo_getchar( struct fifo* pFifo, char* pC)
{
    char c;
    int head = pFifo->head;

    if (fifo_is_empty(pFifo)) return 0;

    // a read barrier may be required here depending on the system
    c = pFifo->buf[head];

    ++head;
    if (head == pFifo->buf_len) {
        head = 0;
    }

    // as far as the write thread is concerned, the char 
    // hasn't been removed until this statement is executed
    pFifo->head = head;

    // a write barrier might be required

    *pC = c;
    return 1;
}

Возможно, было бы более целесообразно использовать «атомарные» API-интерфейсы платформы при обновлении индексов.

Некоторые оптимизации, которые можно выполнить:

  • , если размер fifo ограничен степенью 2, обертывание можно обработать, соответствующим образом замаскировав индексы
  • функции put / get могут быть изменены, или могут быть добавлены дополнительные функции get / put для приема строки или массива байтов данных, а строка / массив может быть скопирована в буфер (или из) буфера fifo более эффективно.

Ключом к этой настройке является то, что читатель может читать данные в fifo, не беспокоясь о том, что писатель перезапишет их, пока индекс head не будет обновлен до тех пор, пока данные не будут обновлены.зачитать.Аналогично для модуля записи - он может записывать в «свободную» часть буфера, пока индекс tail не обновляется до тех пор, пока данные не будут записаны в буфер.Единственное реальное осложнение - убедиться, что соответствующие элементы помечены volatile и названы соответствующие барьеры памяти.

...