Системное программирование в UNIX средствами Free Pascal

       

Первый параметр nfds задает число


uses linux;
Function Select(Nfds:Longint; var readfds,writefds, errorfds:PFDset;
                Var Timeout): Longint;
Первый параметр nfds задает число дескрипторов файлов, которые могут представлять интерес для сервера. Например, если дескрипторы файлов с номерами 0, 1 и 2 присвоены потокам stdin, stdout и stderr соответственно, и открыты еще два файла с дескрипторами 3 и 4, то можно присвоить параметру nfds значение 5. Программист может определять это значение самостоятельно или воспользоваться постоянной FD_SETSIZE, которая определена в файле stdio. Значение постоянной FD_SETSIZE равно максимальному числу дескрипторов файлов, которые могут быть использованы вызовом select.
Второй, третий и четвертый параметры вызова select являются указателями на битовые маски (bit mask), в которых каждый бит соответствует дескриптору файла. Если бит включен, то это обозначает интерес к соответствующему дескриптору файла. Набор readfds определяет дескрипторы, для которых сервер ожидает возможности чтения; набор writefds – дескрипторы, для которых ожидается возможность выполнить запись; набор
errorfds определяет дескрипторы, для которых сервер ожидает появление ошибки или исключительной ситуации, например, по сетевому соединению могут поступить внеочередные данные. Так как работа с битами довольно неприятна и приводит к немобильности программ, существует абстрактный тип данных fdset, а также макросы или функции (в зависимости от конкретной реализации системы) для работы с объектами этого типа. Вот эти макросы для работы с битами файловых дескрипторов:
uses linux;
(* Инициализация битовой маски, на которую указывает fds *)
Procedure FD_ZERO(var fds:fdSet);
(* Установка бита fd в маске, на которую указывает fds *)
Procedure FD_Set(fd:longint;var fds:fdSet);
(* Установлен ли бит fd в маске, на которую указывает fds? *)
Function FD_IsSet(fd:longint;var fds:fdSet):boolean;


(* Сбросить бит fd в маске, на которую указывает fds *)
Procedure FD_Clr(fd:longint;var fds:fdSet);


Следующий пример демонстрирует, как отслеживать состояние двух открытых дескрипторов файлов:
uses linux;
.
.
.
var
  fd1, fd2:longint;
  readset:fdset;
fd1 := fdopen('file1', Open_RDONLY);
fd2 := fdopen('file2', Open_RDONLY);
FD_ZERO(readset);
FD_SET(fd1, readset);
FD_SET(fd2, readset);
case select(5, @readset, nil, nil, nil) of
(* Обработка ввода *)
end;
Пример очевиден, если вспомнить, что переменные fd1 и fd2 представляют собой небольшие целые числа, которые можно использовать в качестве индексов битовой маски. Обратите внимание на то, что аргументам writefds и errorfds в вызове select присвоено значение nil. Это означает, что представляет интерес только чтение из fd1 и fd2.
Пятый параметр вызова select, timeout, является указателем на следующую структуру timeval:
uses linux;
TimeVal  =  Record
   sec,               (* Секунды *)
   usec  :  Longint;  (* и микросекунды *)
end;
Если указатель является нулевым, как в этом примере, то вызов select будет заблокирован до тех пор, пока не произойдет интересующее процесс событие. Если в структуре timeout задано нулевое время, то вызов select завершится немедленно (без блокирования). И, наконец, если структура timeout содержит ненулевое значение, то возврат из вызова select произойдет через заданное число секунд или микросекунд, если файловые дескрипторы неактивны.
Возвращаемое вызовом select значение равно -1 в случае ошибки, нулю – после истечения временного интервала или целому числу, равному числу «интересующих» программу дескрипторов файлов. Следует сделать предостережение: при возврате из вызова select он переустанавливает битовые маски, на которые указывают переменные readfds, writefds или errorfds, сбрасывая маску и снова задавая в ней дескрипторы файлов, содержащие искомую информацию. Поэтому необходимо сохранять копию исходных масок.[11]
Приведем более сложный пример, в котором используются три канала, связанные с тремя дочерними процессами. Родительский процесс должен также отслеживать стандартный ввод.


 (* Программа server - обслуживает три дочерних процесса *)
uses linux,stdio;
const
  MSGSIZE=6;
  msg1:array [0..MSGSIZE-1] of char = 'hello';
  msg2:array [0..MSGSIZE-1] of char = 'bye!!';
type
  tp1=array [0..1] of longint;
  tp3=array [0..2] of tp1;
(* Родительский процесс ожидает сигнала в трех каналах *)
procedure parent(p:tp3);           (* код родительского процесса *)
var
  ch:char;
  buf:array [0..MSGSIZE-1] of char;
  _set, master:fdset;
  i:integer;
begin
  (* Закрыть все ненужные дескрипторы, открытые для записи *)
  for i:=0 to 2 do
    fdclose (p[i][1]);
  (* Задать битовые маски для системного вызова select *)
  FD_ZERO (master);
  FD_SET (0, master);
  for i:=0 to 2 do
    FD_SET (p[i][0], master);
  (* Лимит времени для вызова select не задан, поэтому он
   * будет заблокирован, пока не произойдет событие *)
  _set := master;
  while select (p[2][0] + 1, @_set, nil, nil, nil) > 0 do
  begin
    (* Нельзя забывать и про стандартный ввод,
     * т.е. дескриптор файла fd=0. *)
    if FD_ISSET (0, _set) then
    begin
      write('Из стандартного ввода...');
      fdread (0, ch, 1);
      writeln(ch);
    end;
    for i:=0 to 2 do
    begin
      if FD_ISSET (p[i][0], _set) then
      begin
        if fdread (p[i][0], buf, MSGSIZE) > 0 then
        begin
          writeln('Сообщение от потомка', i);
          writeln('MSG=', buf);
        end;
      end;
    end;
    (* Если все дочерние процессы прекратили работу,
     * то сервер вернется в основную программу
     *)
    if waitpid (-1, nil, WNOHANG) = -1 then
      exit;
  _set := master;
  end;
end;
function child (p:tp1):integer;
var
  count:integer;
begin
  fdclose (p[0]);
  for count:=1 to 2 do
  begin
    fdwrite (p[1], msg1, MSGSIZE);
    (* Пауза в течение случайно выбранного времени *)
    sleep (getpid mod 4);
  end;
  (* Послать последнее сообщение *)
  fdwrite (p[1], msg2, MSGSIZE);


  halt (0);
end;
var
  pip:tp3;
  i:integer;
begin
  (* Создать три канала связи, и породить три процесса. *)
  for i:=0 to 2 do
  begin
    if not assignpipe (pip[i][0],pip[i][1]) then
      fatal ('Ошибка вызова pipe');
    case fork of
      -1:        (* ошибка *)
        fatal ('Ошибка вызова fork');
      0:         (* дочерний процесс *)
        child (pip[i]);
    end;
  end;
  parent (pip);
  halt (0);
end.
Результат данной программы может быть таким:
Сообщение от потомка 0
MSG=hello
Сообщение от потомка 1
MSG=hello
Сообщение от потомка 2
MSG=hello
d   (пользователь нажимает клавишу d, а затем клавишу Return)
Из стандартного ввода d (повторение символа d)
Из стандартного ввода (повторение символа Return)
Сообщение от потомка 0
MSG=hello
Сообщение от потомка 1
MSG=hello
Сообщение от потомка 2
MSG=hello
Сообщение от потомка 0
MSG=bye
Сообщение от потомка 1
MSG=bye
Сообщение от потомка 2
MSG=bye
Обратите внимание, что в этом примере пользователь нажимает клавишу d, а затем символ перевода строки (Enter или Return), и это отслеживается в стандартном вводе в вызове select.
Функция SelectText является модификацией Select, предназначенной для работы с текстовыми файлами:


uses linux;
Function SelectText(var T:Text; TimeOut:PTime):Longint;
SelectText выполняет системный вызов Select для файлов типа Text. Время ожидания может быть указано в параметре TimeOut. Вызов SelectText самостоятельно определяет необходимость проверки чтения и записи в зависимости от того, в каком режиме был открыт файл. При Reset выполняется проверка на чтение, при Rewrite и Append – на запись.
Пример использования SelectText:
Uses linux;
Var tv : TimeVal;
   
begin
  Writeln ('Press the <ENTER> to continue the program.');
  { Wait until File descriptor 0 (=Input) changes }
  SelectText (Input,nil);
  { Get rid of <ENTER> in buffer }
  readln;
  Writeln ('Press <ENTER> key in less than 2 seconds...');
  tv.sec:=2;
  tv.usec:=0;
  if SelectText (Input,@tv)>0 then
    Writeln ('Thank you !')
  else
    Writeln ('Too late !');
end.
Связать SelectText и Select можно с помощью функции GetFS, позволяющей из любой файловой переменной получить дескриптор файла.


uses linux;
Function GetFS(Var F:Any File Type):Longint;
Например:
Uses linux;
begin
  Writeln (' File descriptor of input  ',getfs(input));
  Writeln ('File descriptor of output ',getfs(output));
  Writeln ('File descriptor of stderr ',getfs(stderr));
end.
Пример использования SelectText:
Uses linux;
Var tv : TimeVal;
   
begin
  Writeln ('Press the <ENTER> to continue the program.');
  { Wait until File descriptor 0 (=Input) changes }
  SelectText (Input,nil);
  { Get rid of <ENTER> in buffer }
  readln;
  Writeln ('Press <ENTER> key in less than 2 seconds...');
  tv.sec:=2;
  tv.usec:=0;
  if SelectText (Input,@tv)>0 then
    Writeln ('Thank you !')
  else
    Writeln ('Too late !');
end.

Содержание раздела