01signal.com

Xillybus의 device files에 액세스

소개

이 페이지는 Xillybus' programming guide for Linux를 기반으로 합니다. 아래 항목에 대한 보다 포괄적인 보기는 이 가이드를 참조하는 것이 좋습니다. Windows에 대한 유사한 가이드 도 있습니다.

아직 "Hello, World" 테스트를 수행하지 않은 경우 먼저 수행하는 것이 좋습니다.

Xillybus IP core 와의 통신은 host의 device files를 통해 이루어집니다. 이러한 device files는 일반 파일처럼 액세스됩니다. 그러나 일반 파일과 달리 device file은 디스크에 저장된 data를 나타내지 않습니다. 대신 device file 에 대한 읽기 및 쓰기는 I/O 작업으로 이어집니다.

따라서 거의 모든 프로그래밍 언어로 Xillybus의 device files 에 액세스할 수 있습니다. 이 목적으로 Linux command-line utilities를 사용하는 것도 가능합니다. 따라서 이 주제를 논의할 필요가 있는 이유가 무엇인지 궁금할 수 있습니다. 파일을 올바르게 읽고 쓰는 방법을 알고 있다면 Xillybus로 작업하는 방법을 알고 있는 것입니다.

실제로 API에 대한 자세한 지식 없이도 일반 파일에 액세스하는 프로그램을 작성할 수 있습니다. 그러나 이것은 I/O로 작업하기에는 충분하지 않습니다. 하드웨어와의 상호 작용은 일반 파일에서는 거의 발생하지 않는 상황을 만듭니다. 프로그래머로서 API의 도움으로 이러한 상황을 처리하는 방법을 알아야 합니다.

따라서 이 페이지의 대부분은 일반 파일 액세스와 관련된 항목에 전념합니다. device files 와의 차이점은 API를 사용할 때 실수에 대해 덜 관대하다는 것입니다.

API 에 대한 이해 부족은 주로 두 가지 종류의 혼란을 야기할 수 있습니다.

프로그래밍 언어 및 운영 체제

Windows를 사용하면 C, C++, C#, Python, Perl 및 Cygwin와 함께 제공되는 모든 프로그래밍 언어에 문제가 없습니다. 그러나 일부 도구(예: MATLAB)는 Xillybus의 device files에서 작동하지 않을 수 있습니다. 이러한 도구는 device file이 일반 파일이 아님을 감지하고 이를 오류로 간주합니다. 일반적으로 low-level I/O용으로 의도된 extensions을 사용하는 이 상황에 대한 해결 방법이 있습니다.

Linux를 사용하면 파일에 액세스할 수 있는 모든 도구 또는 프로그래밍 언어가 Xillybus에서 작동합니다.

아래 논의는 C 언어를 기반으로 하지만 주제는 모든 프로그래밍 언어와 관련이 있습니다.

예제 코드

Xillybus웹사이트에서 다운로드할 수 있는 코딩 예제가 있습니다. 이 예제는 C로 작성되었으며 저수준 API를 올바르게 사용하는 방법을 보여줍니다.

예제를 다운로드하려면 demo bundle을 다운로드한 웹 페이지(예: Xillybus용 페이지 또는 XillyUSB용 페이지 )로 이동하십시오.

Linux를 사용하는 경우 Linux driver를 다운로드하십시오. 예제 코드는 동일한 .tar.gz 파일에 포함되어 있습니다.

Windows를 사용하는 경우 Xillybus package for Windows를 다운로드하십시오.

어느 쪽이든 예제 코드는 demoapps/ directory내부에 있습니다. 이 코드에서는 작은 buffer가 할당되기 때문에(128 bytes) 각 I/O operation 에서 읽거나 쓰는 data 의 양이 적습니다. 이것의 목적은 단지 코드를 더 간단하게 만드는 것입니다. 실제 애플리케이션에서는 더 큰 buffer가 권장됩니다(일반적으로32 kBytes가 좋은 선택임).

Linux 와 Windows 의 차이점은 작지만 중요합니다. 특히:

즉, 예제 코드의 원리는 정확히 동일합니다.

Buffered file I/O

프로그래밍 언어는 일반적으로 파일 액세스를 위해 두 개의 별도 APIs를 제공합니다. 하나의 상위 수준 API및 하나의 하위 수준 API. 높은 수준의 API는 작업하기가 더 쉽기 때문에 더 일반적으로 사용됩니다.

예를 들어 C 언어에서 상위 수준의 API는 fopen(), fread(), fwrite(), fprintf(), fclose() 등으로 구성됩니다. 하위 수준의 API는 open(), read(), write(), close() 등으로 구성됩니다. 이 두 APIs 의 차이점은 다음과 같습니다. functions의 이름에 약간의 차이가 있을 뿐입니다. 높은 수준의 API는 user-space RAM buffers를 제공합니다. 이러한 buffers는 C run-time library에 의해 구현됩니다. kernel에서 driver 로 제어되는 DMA buffers와 혼동하지 마십시오.

가장 중요한 차이점은 fwrite()의 동작입니다. 이 function 에 대한 call 의 결과는 data가 user-space buffer에 저장될 수 있습니다. FPGA 로의 전송은 나중까지 지연될 수 있습니다. 실제로 data는 파일이 닫힐 때까지 fwrite()의 buffer 에 무기한 남아 있을 수 있습니다. data가 파일에 작성되었지만 아무 일도 일어나지 않았기 때문에 이것은 Xillybus의 버그처럼 보일 수 있습니다.

따라서 하위 수준(non-buffering) API를 사용하는 것이 좋습니다. 대부분의 프로그래밍 언어가 높은 수준의 API사용을 촉진하더라도 이는 일반적으로 가능합니다. 도구 또는 프로그래밍 언어가 하위 수준 API를 지원하지 않는 경우 대신 사용할 수 있는 API를 사용하십시오. 이 경우 I/O가 발생하는 시점을 제어할 수 없다는 점을 기억하는 것이 중요합니다. 그럼에도 불구하고 이것은 예를 들어 data acquisition와 같은 많은 응용 프로그램에서 충분합니다.

다시 한 번 말하지만 buffered I/O를 Xillybus의 buffers와 혼동해서는 안 됩니다. 가장 중요한 것은 Xillybus의 buffers때문에 data가 무한정 멈추지 않는다는 것입니다. 이에 대해서는 아래에서 zero-length write()와 관련하여 자세히 설명합니다.

device file에서 읽기: 기본 사항

device file 에서 읽기 위한 예제 코드는 streamread.c입니다. 이 프로그램은 demoapps/ directory에서 찾을 수 있습니다. 그러나 아래에 표시할 코드 스니펫은 다른 프로그램에서 가져온 것입니다. memread.c (동일한 directory에서). 이 프로그램은 다른 목적을 위한 것이지만 allread()라는 이름의 function을 포함하고 있습니다. 이 function로 몇 가지 주제를 시연하는 것이 더 편리합니다.

다음 명령으로 파일을 열었다고 가정해 보겠습니다.

int fd, len;
char *buf;

fd = open("/dev/xillybus_read_32", O_RDONLY);

이제 파일에서 @len bytes를 buffer로 읽어들이려고 합니다. 그러나 부분적인 결과는 허용되지 않아야 합니다. 우리는 항상 필요한 양의 data를 읽는 function을 원합니다.

이것은 allread()가 다음과 같이 사용될 때 하는 일입니다.

allread(fd, buf, len);

이 function은 다음과 같이 정의됩니다.

void allread(int fd, unsigned char *buf, int len) {
  int received = 0;
  int rc;

  while (received < len) {
    rc = read(fd, buf + received, len - received);

    if ((rc < 0) && (errno == EINTR))
      continue;

    if (rc < 0) {
      perror("allread() failed to read");
      exit(1);
    }

    if (rc == 0) {
      fprintf(stderr, "Reached read EOF\n");
      exit(1);
    }

    received += rc;
  }
}

이 function은 항상 bytes의 요청 번호를 읽습니다. 이것이 불가능할 경우 function은 컴퓨터 프로그램을 종료시킵니다. 이것은 정상적인 사용 시나리오에 대해 너무 극단적일 수 있습니다. allread()는 저수준 API로 파일에 액세스하는 방법에 대한 간단한 데모로 취급되어야 합니다.

이 function에 대해 설명하겠습니다. 첫 번째 흥미로운 부분은 다음과 같습니다.

rc = read(fd, buf + received, len - received);

@received는 첫 번째 loop동안 0과 같습니다. 따라서 이 행은 다음과 동일합니다.

rc = read(fd, buf, len);

read()는 file descriptor (@fd)에서 @len bytes를 읽고 data를 buffer (@buf)에 저장하려고 시도합니다.

Xillybus device file의 경우: read()는 요청한 data (@len bytes)의 양을 사용할 수 없는 경우 최대 10 ms 까지 기다립니다. 이 짧은 시간이 지나면 함수는 필요한 것보다 적은 data 로 반환됩니다(단, 최소한 하나의 byte). data가 전혀 없는 경우 read()는 data가 FPGA 에서 도착할 때까지 무기한 대기합니다(예외가 있으며 자세한 내용은 아래 참조). 이 동작은 Xillybus의 driver 에만 해당됩니다(그럼에도 불구하고 표준 API를 준수함).

read()가 무언가를 읽을 수 있는 경우 @rc는 읽은 bytes 의 수와 같습니다. 즉, @rc는 양수이고 loop 의 모든 if-statements는 건너뜁니다. 따라서 이것은 다음에 발생합니다.

received += rc;

따라서 @received 에는 항상 지금까지 읽은 bytes 의 총 개수가 포함됩니다. @rc가 @len (요청된 bytes 의 수)보다 작은 것은 완전히 합법적이고 정상입니다.

while-loop은 @received (읽은 bytes 의 총 수)가 @len에 도달할 때까지 계속됩니다. 이것은 while statement에 반영됩니다.

while (received < len) { ... }

따라서 필요한 경우 더 많은 data를 읽습니다.

rc = read(fd, buf + received, len - received);

이번에는 buffer 의 시작점을 @received로 이동합니다. 요청된 bytes 의 수도 같은 수로 줄어듭니다. 이러한 조정은 이것이 data를 읽으려는 반복적인 시도라는 것을 반영할 뿐입니다.

read()가 아무것도 읽지 않을 때

지금까지는 read()가 data를 읽을 수 있을 때 어떤 일이 발생하는지에 초점을 맞추었습니다. 그러나 read()가 아무것도 읽지 않는 세 가지 상황이 있습니다. 이러한 각 상황은 자체 if-statement에 의해 처리됩니다.

POSIX signals

CTRL-C를 눌러 프로그램을 중지하면 운영 체제는 POSIX signal을 process로 보냅니다. 이것은 프로그램을 종료시키는 메커니즘입니다. 동일한 목적으로 "kill" 명령을 사용할 때도 마찬가지입니다. 그러나 대부분의 상황에서 무시해야 하는 다른 유형의 signals 도 많이 있습니다.

그렇다면 process가 read()에 대한 함수 호출 도중에 signal을 수신하면 어떻게 될까요? Linux의 규약에 따르면 read()는 제어권을 주 프로그램으로 즉시 반환해야 합니다. 이 일이 발생하기 전에 read()가 data를 읽을 수 있었다면 특별한 일이 발생하지 않습니다. @rc는 bytes의 번호를 포함하며 signal이 수신되었다는 표시는 없습니다.

그러나 새 data가 도착하지 않은 경우 @rc는 음수가 되고 @errno는 EINTR와 같습니다. 이 상황을 처리하는 표준 방법은 코드에 표시된 것과 같습니다. 아무 일도 없었던 것처럼 행동하고 다시 시도하십시오.

if ((rc < 0) && (errno == EINTR))
  continue;

이것은 signal이 무시된다는 의미는 아닙니다. 예를 들어 signal이 발생한 이유가 사용자가 CTRL-C를 눌렀기 때문이라면 프로그램은 정상적으로 종료됩니다. 이를 처리하는 또 다른 메커니즘이 있습니다. 이 if-statement 의 목적은 극적인 원인이 아닌 signals를 처리하는 것입니다. continue-statement는 process가 이런 종류의 signal을 수신하더라도 이상한 일이 발생하지 않도록 합니다.

예를 들어, process가 CTRL-Z로 중지된 경우 실행이 재개될 때 프로그램이 계속 실행되도록 하려면 이 if-statement가 필요합니다. 또한 사람의 개입 없이 도착할 수 있는 몇 가지 다른 signals가 있습니다.

실제 error

당연히 data를 읽으려고 시도하는 동안 문제가 발생할 수 있습니다. 이 경우 @rc는 음수가 되고 @errno의 값은 EINTR가 아닌 다른 값이 됩니다. 예제 코드는 단순히 이 오류를 보고하고 컴퓨터 프로그램을 종료합니다.

if (rc < 0) {
  perror("allread() failed to read");
  exit(1);
}

EOF

파일 끝에 도달했기 때문에 read()가 data를 제공할 수 없는 경우 이 function은 값 0을 반환합니다. 이것은 일반 파일의 경우에도 마찬가지입니다. 그러나 Xillybus 에는 data stream이 종료되었음을 선언하는 기능도 있습니다. 동작은 동일합니다.

관련 코드는 다음과 같습니다.

if (rc == 0) {
  fprintf(stderr, "Reached read EOF\n");
  exit(1);
}

이 역시 오류로 처리되며 컴퓨터 프로그램이 종료됩니다. 요청된 data (@len bytes)의 양을 읽기 전에 EOF 에 도달했음을 의미합니다. 이 if-statement는 @received가 @len보다 작은 경우에만 도달할 수 있습니다.

allread() 의 기본 개념은 항상 필요한 양의 data를 읽는다는 것입니다. 이것이 불가능할 경우 이 기능은 컴퓨터 프로그램을 중지시킵니다.

다른 프로그래밍 언어와의 관련성

위의 예제 코드는 C로 작성되었지만 프로그래밍 언어와 관계없이 관련된 몇 가지 중요한 사항을 보여줍니다.

device file에 쓰기

파일에 쓰기 위한 저수준 API는 파일에서 읽는 것과 거의 동일합니다. 이를 입증하기 위해 allwrite()라는 이름의 function은 streamwrite.c에서 찾을 수 있습니다.

void allwrite(int fd, unsigned char *buf, int len) {
  int sent = 0;
  int rc;

  while (sent < len) {
    rc = write(fd, buf + sent, len - sent);

    if ((rc < 0) && (errno == EINTR))
      continue;

    if (rc < 0) {
      perror("allwrite() failed to write");
      exit(1);
    }

    if (rc == 0) {
      fprintf(stderr, "Reached write EOF (?!)\n");
      exit(1);
    }

    sent += rc;
  }
}

위에 표시된 allread() 와 비교하십시오. 차이점은 세 가지뿐입니다.

따라서 원칙적으로 쓰기와 읽기 사이에는 차이가 없습니다.

파일에 쓸 때 EOF 에는 의미가 없기 때문에 @rc는 0이 되어서는 안 됩니다. POSIX standard에 따르면 @rc는 write()가 0인 bytes를 쓰도록 요청된 경우에만 0일 수 있습니다. 하지만 이 while loop에서는 이런 일이 절대 일어나지 않습니다.

요약하면 allwrite()는 항상 요청된 bytes 의 수를 기록합니다. 유일한 대안은 process를 종료하는 것입니다. 즉, device file이 다음과 같은 방식으로 열렸다고 가정합니다.

int fd, len;
char *buf;

fd = open("/dev/xillybus_write_32", O_WRONLY);

@buf 에서 @len bytes 쓰기는 다음과 같이 수행됩니다.

allwrite(fd, buf, len);

allread() 에 대해 위에서 언급한 모든 내용은 allwrite() 에도 적용됩니다. 여기에는 다른 프로그래밍 언어와의 관련성이 포함됩니다.

Zero-length write

bytes가 0인 write() 에 함수 호출을 할 수 있습니다. 표준 API는 특정 경우에 어떤 일이 일어날지 말하지 않습니다. 그러나 분명히 이것은 data가 작성되지 않음을 의미합니다.

이러한 종류의 함수 호출은 Xillybus device file에 대해 특별한 의미가 있습니다. bytes를 0으로 쓰는 것은 flush를 요청하는 것을 의미합니다. 이것의 의미를 이해하기 위해 먼저 data가 device file에 기록될 때 어떤 일이 발생하는지 살펴보겠습니다.

device file이 asynchronous stream라고 가정해 봅시다. 이 용어는 다른 페이지 에 간략하게 설명되어 있으며 자세한 내용은 설명서 에 나와 있습니다.

data가 device file ( write() 사용)에 기록되면 Xillybus driver는 이 data를 RAM buffer에 저장합니다. 이 data 의 일부 또는 전부가 즉시 FPGA 로 전송될 수 있습니다. 그러나 일반적으로 말하면 data 의 양이 buffer에 남아 있을 수 있으며 그럼에도 불구하고 기능 호출을 수행한 컴퓨터 프로그램은 계속됩니다. 이 메커니즘의 목적은 특히 write()에 대한 함수 호출이 많은 경우 성능을 향상시키는 것입니다.

그렇다면 driver의 RAM buffer 에 있는 data는 언제 FPGA로 전송됩니까? 네 가지 가능한 상황이 있습니다.

따라서 data는 driver의 buffer 에 너무 오래 붙어 있지 않습니다. 이는 data가 항상 10 ms기간 내에 FPGA 로 전송되기 때문입니다. 그러나 일부 응용 프로그램에서는 이러한 지연도 허용되지 않습니다. 이 경우 zero-length write()를 사용하여 나머지 data를 즉시 보내도록 요청할 수 있습니다.

다음은 C 언어에서 이를 수행하는 방법입니다.

write(fd, NULL, 0);

address 에서 buffer는 NULL입니다. 쓸 bytes 의 수가 0이므로 괜찮습니다. 그러나 이 함수 호출은 요청의 성공을 보장하지 않습니다. 성공할 가능성이 매우 높지만 올바른 방법은 다음과 같습니다.

while (1) {
  rc = write(fd, NULL, 0);

  if ((rc < 0) && (errno == EINTR))
    continue; // Interrupted. Try again.

  if (rc < 0) {
    perror("flushing failed");
    break;
  }

  break; // Flush successful
}

이 모든 것은 asynchronous stream에 대해 언급되었습니다. device file이 synchronous stream인 경우 data는 write()에 대한 함수 호출의 결과로 항상 즉시 FPGA 로 전송됩니다. 그 외에도 write()는 data가 FPGA 에 도달할 때까지 기다렸다가 돌아옵니다( zero-length write()는 그렇게 하지 않음).

따라서 zero-length write()는 asynchronous streams와만 관련이 있습니다. 이 기능은 FPGA와의 통신 속도를 저하시키므로 필요한 경우가 아니면 사용해서는 안 됩니다.

요약

이미 언급했듯이 위에 작성된 거의 모든 내용은 모든 파일에 액세스하는 데 적합합니다. 몇 가지 주제만 Xillybus에만 적용되었습니다.

FPGA와 통신하는 동안 일관된 동작을 보장하려면 이러한 지침을 따르는 것이 중요합니다. 이러한 주제를 고려하지 않고 작성된 컴퓨터 프로그램은 가끔 오류가 발생할 수 있습니다. 이러한 실패는 종종 FPGA 또는 driver의 문제로 보입니다. 따라서 적절한 프로그래밍 기술을 사용하면 많은 혼란과 불필요한 노력을 덜 수 있습니다.

이 페이지는 영어에서 자동으로 번역됩니다. 불분명한 사항이 있으면 원본 페이지를 참조하십시오.
Copyright © 2021-2024. All rights reserved. (6f913017)