Импорт функции задержки 'C' в Haskell с использованием FFI

В библиотеке wireingPi 'C' есть функция, называемая delay с типом

void delay(unsigned int howLong);

Эта функция задерживает выполнение кода на howLong миллисекунды. Я написал код привязки на Haskell, чтобы иметь возможность вызывать эту функцию. Код Haskell выглядит следующим образом:

foreign import ccall "wiringPi.h delay" c_delay :: CUInt -> IO ()
hdelay :: Int -> IO ()
hdelay howlong = c_delay (fromIntegral howlong)

После этого я написал простую программу на Haskell для вызова этой функции. Просто код haskell выглядит следующим образом.

--После импорта соответствующих библиотек я сделал

main = wiringPiSetup
    >> delay 5000

Но задержки не происходит, точнее исполняемый файл, сгенерированный компилятором ghc, завершает работу сразу.

Может ли кто-нибудь сказать мне, что здесь может пойти не так? Небольшой толчок в правильном направлении поможет.

С уважением.


person Jay    schedule 05.03.2013    source источник
comment
Не знаю в чем причина, но (используя usleep, т.к. у меня нет вайринга Пи) с непотоковой РТС, для ghc‹=7.2.*, ждет пока не закончится сон. С прошитой RTS или с GHC ›= 7.4 он сразу же завершается. Вероятно, ошибка, но я не знаю, в старых GHC или в новых.   -  person Daniel Fischer    schedule 06.03.2013
comment
Разве заголовок WiringPi не является wirePi.h?   -  person Michael Steele    schedule 06.03.2013
comment
@DanielFischer: не должно ли это быть ошибкой в ​​новых GHC/поточных RTS? Разве такое поведение не означает, что невозможно безопасно вызвать функцию как последнее действие в программе, потому что нет никакой гарантии, что она завершится?   -  person John L    schedule 06.03.2013
comment
@Майкл Стил Да. Это wiringPi.h. Спасибо что подметил это. Я отредактирую исходный вопрос, чтобы отразить изменение.   -  person Jay    schedule 06.03.2013
comment
@JohnL Я бы тоже так сказал, но я оставил лазейку на случай, если это было задумано.   -  person Daniel Fischer    schedule 06.03.2013
comment
@DanielFischer, конечно, это всегда возможно, но если это предполагаемое поведение, я бы посоветовал его переосмыслить.   -  person John L    schedule 06.03.2013
comment
Заявка отправлена, посмотрим, что скажет штаб.   -  person Daniel Fischer    schedule 06.03.2013


Ответы (1)


Пожалуйста, игнорируйте часть в кавычках и смотрите обновление ниже — я сохраняю исходное решение, не связанное с ним, из-за комментариев, связанных с ним.

Вы должны пометить import как unsafe, так как вы хотите, чтобы основной поток блокировался во время выполнения функции (см. комментарий @carl ниже). По умолчанию импорт является безопасным, а не unsafe. Таким образом, изменение сигнатуры функции на это должно привести к блокировке основного потока:

foreign import ccall unsafe "wiring.h delay" c_delay :: CUInt -> IO ()

Кроме того, если вы планируете писать многопоточный код, Документы GHC для многопоточных FFI >очень полезны. Это также кажется хорошим началом.

Обновить

Похоже, такое поведение связано с обработкой сигнальных прерываний (если я правильно помню, это было добавлено в GHC 7.4+ для исправления некоторых ошибок). Подробнее здесь: http://hackage.haskell.org/trac/ghc/wiki/Commentary/Rts/Signals

Обратите внимание на комментарий на странице выше: Signal handling differs between the threaded version of the runtime and the non-threaded version.

Подход 1. Обработка прерывания сигнала в коде FFI. Ниже приведен игрушечный код, который обрабатывает прерывание в спящем режиме. Я тестировал его на Linux 2.6.18 с ghc 7.6.1.

C-код:

/** ctest.c **/
#include <unistd.h>
#include <stdio.h>
#include <time.h>

unsigned delay(unsigned sec)
{
  struct timespec req={0};
  req.tv_sec = sec;
  req.tv_nsec = 0;

  while (nanosleep(&req, &req) == -1) {
    printf("Got interrupt, continuing\n");
    continue;
    }
  return 1;
}

Хаскель-код:

{-# LANGUAGE ForeignFunctionInterface #-}
-- Filename Test.hs
module Main (main) where
import Foreign.C.Types

foreign import ccall safe "delay" delay :: CUInt -> IO CUInt

main =  do
    putStrLn "Sleeping"
    n <- delay 2000
    putStrLn $ "Got return code from sleep: " ++ show n

Теперь, после компиляции с помощью ghc 7.6.1 (команда: ghc Test.hs ctest.c), он ожидает завершения сна и печатает сообщение каждый раз, когда получает сигнал прерывания во время сна:

./Test
Sleeping
Got interrupt, continuing
Got interrupt, continuing
Got interrupt, continuing
Got interrupt, continuing
....
....
Got return code from sleep: 1

Подход 2 – отключите SIGVTALRM перед вызовом кода FFI и снова включите:

Я не уверен, каковы последствия отключения SIGVTALRM. Это альтернативный подход, который отключает SIGVTALRM во время вызова FFI, если вы не можете изменить код FFI. Таким образом, код FFI не прерывается во время сна (при условии, что это SIGVTALRM вызывает прерывание).

{-# LANGUAGE ForeignFunctionInterface #-}
-- Test.hs
module Main (main) where
import Foreign.C.Types
import System.Posix.Signals

foreign import ccall safe "delay" delay :: CUInt -> IO CUInt

main =  do
    putStrLn "Sleeping"
    -- Block SIGVTALRM temporarily to avoid interrupts while sleeping
    blockSignals $ addSignal sigVTALRM emptySignalSet
    n <- delay 2
    putStrLn $ "Got return code from sleep: " ++ show n
    -- Unblock SIGVTALRM
    unblockSignals $ addSignal sigVTALRM emptySignalSet
    return ()
person Sal    schedule 05.03.2013
comment
Чего-чего? Вещи, которые блокируют, должны быть safe, а не unsafe. Вещи, которые являются unsafe, предотвращают переключение этого контекста выполнения на другие потоки, пока они блокируются. В качестве побочного эффекта это означает, что они также предотвращают запуск операций GC, пока они блокируются, что означает, что они могут приостанавливать все потоки в RTS. - person Carl; 05.03.2013
comment
@Carl, хороший момент о переключении контекста выполнения. Насколько я понимаю, основное различие между вызовами safe и unsafe заключается в том, что safe следует использовать для любого кода, который может выполнять обратный вызов. Я обновлю формулировку, чтобы использовать переключатель контекста выполнения. - person Sal; 05.03.2013
comment
Документация GHC гласит: Это означает, что если вам нужно сделать внешний вызов функции, которая занимает много времени или блокируется на неопределенный срок, вы должны пометить ее safe и использовать -threaded. Поэтому я не думаю, что следует использовать unsafe. - person nymk; 05.03.2013
comment
@nymk, верно, но имейте в виду, что unsafe можно использовать, если время выполнения является однопоточным, и вызов внешней функции должен выполняться полностью до продолжения основного потока, как это, по-видимому, и является целью вопроса. Если вы сделаете это safe и threaded, вы также должны добавить некоторый механизм (например, mvar) для ожидания завершения вызовов функций, чтобы основной поток не завершился преждевременно до того, как все эти вызовы FFI в других потоках будут выполнены. safe и threaded действительно будут следующим шагом в эволюции после того, как вы узнаете разницу между функциями safe и unsafe в однопоточном режиме. - person Sal; 05.03.2013
comment
Я не думаю, что это правильно. Действия ввода-вывода должны выполняться одинаково независимо от того, помечен ли вызов как безопасный или небезопасный. Разница, насколько я понимаю, заключается в том, что небезопасные вызовы могут блокировать все потоки, тогда как безопасные вызовы блокируют только поток, выполняющий вызов. - person Michael Steele; 06.03.2013
comment
@sal: ваше беспокойство вполне обосновано, но небезопасный иностранный импорт работает иначе. Благодаря многопоточной среде выполнения и нескольким возможностям (например, -N(>1)), даже если вызов unsafe, вызовы FFI в других потоках могут выполняться до возврата небезопасного вызова. Я думаю, что Даниэль Фишер прав. Такое поведение кажется ошибочным; использование небезопасного импорта для обхода этого кажется довольно плохим кладом. - person John L; 06.03.2013
comment
@JohnL, согласен, поведение кажется глючным. Я написал небольшой тестовый код, и основной поток сразу закрылся. Сильно отличается от поведения FFI, которое я видел в 7.2 и более ранних версиях. - person Sal; 06.03.2013
comment
@JohnL Использование unsafe даже не работает в моих тестах, программа в любом случае завершается без сна. Странно то, что если у меня есть оболочка вокруг usleep, которая возвращает значение, возвращаемое значение также доставляется немедленно, не дожидаясь сна. - person Daniel Fischer; 06.03.2013
comment
@DanielFischer, ага, похоже, вы обнаружили что-то уникальное для взаимодействия ussleep и RTS FFI. Вы должны сообщить об этом в GHC trac. - person Sal; 06.03.2013
comment
Не уверен, что это актуально. Но мой первоначальный вопрос касался ARM-порта GHC. Я полагаю, что это не должно иметь значения, но версия, которая продемонстрировала это ошибочное поведение, представляет собой порт ARM GHC версии 7.4.1, этап 2, загруженный GHC версии 7.4.1. - person Jay; 07.03.2013