Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Tests
  Test Red Hat
  Test
  Test Perl
  Test King
  Test OS
  Test 486
  Test ANSI C
=>  Test LPG
  Test Kernel
  Test Python
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Linux Kernel 2.6...3415 
 MINIX...3087 
 Solaris...2962 
 LD...2956 
 William Gropp...2277 
 Trees...2142 
 Rodriguez 6...2072 
 C++ Templates 3...1983 
 Kamran Husain...1934 
 Secure Programming for Li...1852 
 Максвелл 5...1763 
 DevFS...1753 
 Part 3...1737 
 Go Web ...1674 
 Ethreal 4...1668 
 Стивенс 9...1664 
 Stein-MacEachern-> Час...1662 
 Arrays...1641 
 Максвелл 1...1636 
 ffmpeg->tutorial...1586 
 
  01.01.2024 : 3621733 посещений 

 

 

Стивенс , Advanced Programming in the UNIX

Глава 3 , File I/O

1. Основные базовые функции I/O ? 2. Системные вызовы dup , dup2 ? 3. Вызовы sync, fsync ? 4. Вызов fcntl ?

Глава 4 , Файлы и директории

5. stat , fstat ? 6. Какие есть типы файлов ? 7. Функция access ? 8. Функции umask , chmod , fchmod , chown , fchown? 9. Функции link , unlink , remove , rmdir , rename ? 10. Функции для чтения директорий ? 11. Может ли размер директории и симлинка равен нулю ? 12. Есть ли ограничение на глубину вложенности ?

Глава 5 , Стандартная потоковая библиотека streams I/O

13. Потоки ? 14. Функции работы с потоком ? 15. Форматирование ввода и вывода ? 16. Функции работы со временем ?

Глава 7 , Процессы

17. Функции выделения памяти ? 18. Функции работы с переменными среды ? 19. Функции wait и waitpid ?

Глава 10 , Сигналы

20. Общая концепция сигналов ? 21. Функции kill и raise ? 22. Функция sigaction?

Глава 11 , Потоки

23. Тредовые функции ? 24. Мютексы ? 25. Блокировки на чтение-запись ? 26. Условные переменные ? 27. Атрибуты треда ?

K.A.Robbins , Unix™ Systems Programming: Communication, Concurrency, and Threads

Часть 4 , I/O

1. Функции open , close , read , write ? 2. Функция select ? 3. Функция poll ? 4. Стандартная библиотека I/O ? 5. Функция dup2 ? 6. Функция fcntl ? 7. Функции работы с файловой системой ? 8. Terminal control ?

Часть 12 , Потоки

9. Обработка файловых дескрипторов ? 10. Функции управления тредов ? 11. Атрибуты треда ? 12. Мьютексы ? 13. Condition variables ? 14. Сигналы ? 15. Блокировки на чтение-запись ? 16. Дедлоки ? 17. Неименованный семафор ? 18. Именованный семафор ?

Андрей Боровский , программирование под линукс

1. Написать аналог команды set ? 2. Написать программу , распечатывающую каталог с помощью функции scandir? 3. Написать программу , распечатывающую каталог с помощью функций opendir(), readdir() , closedir() ? 4. Написать программу , создающую разреженный файл ? 5. Написать программу с использованием функции fcntl ? 6. Написать программу с использованием функций popen и pclose ? 7. Написать программу с использованием функций pipe ? 8. Написать программу с использованием функции execve ? 9. Написать программу с использованием mkfifo ? 10. Каковы основные виды сокетов ? 11. Как работает простое клиент-серверное приложение для сетевых сокетов ? 12. Как называется образ памяти терминированной программы , сброшенный на диск? 13. Какие бывают сигналы ? 14. Напишите программу-обработчик сигналов с помощью функции sigaction ? 15. Что такое процесс и какой структурой он описывается ? 16. Какие есть особенности у fork ? 17. Как работают функции семейсива exec * ? 18. Какие функции предназначены для работы с переменными окружения? 19. Написать пример с помощью execvp , которой передается имя программы в качестве аргумента ? 20. Какие исторически виды потоков бывают в линуксе ? 21. В чем особенность линуксовых потоков ? 22. Как получить идентификатор потока ? 23. Какая функция создает поток ? 24. Напишите программу , создающую поток ? 25. Как досрочно завершить поток ? 26. Как можно использовать семафоры для синхронизации потоков ? 27. как использовать мьютексы для синхронизации потоков ? 28. Что такое отделенные и присоединяемые потоки ? 29. Что такое демон ? 30. В чем особенность программирования демонов ? 31. Как структура termios управляет терминалом ? 32. Написать программу ввода паролей ? 33. Что такое curses и ncurses ? 34. Основные принципы ncurses ? 35. Напишите простую программу на ncurses ? 36. Написать программу манипуляции с окнами на ncurses ? 37. Как управлять цветом в ncurses ? 38. Управление мышью в ncurses ?

Linux Programmer's Guide : S. Goldt, S. Meer, S. Burkett, M. Welsh

IPC

1. Что делает syscall ? 2. Что делает ioctl ? 3. Какие методы IPC вы знаете ? 4. Как работают полудуплексные каналы pipes ? 5. Как создать полудуплексные каналы pipes на си с помощью вызова pipe()? 6. Как создать полудуплексные каналы pipes на си с помощью вызова popen()? 7. Где определяется максимальные размеры буфера для канала ? 8. Чем отличаются полудуплексные каналы pipes от именованных каналов fifo ? 9. Как создать именованный канал ? 10. Как провести операцию с fifo ? 11. Каковы основные методы System V IPC ? 12. Как получить список объектов System V IPC. ? 13. Как удалить объект IPC ? 14. Что такое очереди сообщений queue IPC ? 15. Что такое буфер сообщений IPC ? 16. Где хранится очередь сообщений IPC ? 17. Где хранится идентификатор каждого сообщения IPC ? 18. Где хранится информация о правах доступа каждого обьекта IPC ? 19. Как создать очередь или обратиться к уже существующей ? 20. Как поставить сообщение в очередь ? 21. Как управляет очередями команда msgсtl() ? 22. Как работать с очередями IPC из командной строки ? 23. Что такое семафоры ? 24. Как определяется идентификатор каждой группы семафоров в ядре ? 25. Как определяется в ядре каждый семафор ? 26. Как получить доступ к группе семафоров ? 27. Как изменить значение семафора ? 28. Как совершить операции над группой семафоров ? 29. Как управлять семафорами из командной строки ? 30. Что такое Разделяемая память IPC ? 31. Как определить разделяемый сегмент памяти IPC? 32. Как создать сегмент памяти ? 33. Как привязать сегмент памяти к адресу ? 34. Как прочитать-записать в сегменту памяти ? 35. Как работать с разделяемой памятью из командной строки ?

Terminal

36. Основы вывода в терминале : termcap , terminfo , curses , ncurses ? 37. Основы ncurses ? 38. Работа с окнами в ncurses ? 39. Функции вывода в ncurses ? 40. Как рисовать линии в ncurses ? 41. Как сделать очистку - обновление экрана в ncurses ? 42. Как обновить терминал в ncurses ?

GCC

43. Как работает утилита configure ? 44. Как работает утилита libtool ? 44-1. Как работает отладчик GDB ?

GUI

45. Приведите простейший пример программы на GTK+ 2.0 ? 46. Приведите простейший пример программы на QT ?

Майкл К. Джонсон Разработка приложений в среде Линукс

1. Типы файлов ? 2. Файловые дескрипторы? 3. Написать программу , создающую файл ? 4. Поиск seek ? 5. Написать аналог утилиты cat ? 6. Функция для определения прав доступа к файлу ? 7. Функции для смены владельца и группы ? 8. Ссылки ? 9. Дублирование файловых дескрипторов ? 10. Жизненный цикл сигнала ? 11. Посылка сигналов ? 12. Перехват сигналов ? 13. Ожидание сигналов ? 14. Системный вызов poll ? 15. Системный вызов select ? 16. Написать сравнительную программу с использованием poll и select ? 17. Функции readv , writev ? 18. Функции для работы с каталогом ? 19. Написать реализацию find ? 20. tty ? 21. termios ? 22. Псевдотерминалы pty ? 23. Сокеты ? 24. IPV4 , IPV6 ? 25. Время ? 26. Таймеры ? 27. Случайные числа ? 28. Регулярные выражения ? 29. Slang ? 30. Динамическая загрузка библиотек ?

Ответы

1. Все файловые операции сводятся в основном к вызову функций
   open
   read
   write
   lseek
   close
 
  Дескриптор - число , возвращаемое функциями open , create .
 По умолчанию 0 - стандартный ввод , 1 - стандартный вывод , 2 - ошибка .
 Это константы STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO.
 Максимальное значение дескриптора - это OPEN_MAX.
 
 Функция open :
 int open(const char *pathname, int oflag,  ... /* mode_t mode   */ );
  2-й параметр может приобретать одно из 3-х обязательных значений :
   O_RDONLY - только на чтение - 0
   O_WRONLY - только на запись  - 1
   O_RDWR   - на чтение и на запись  - 2
 Доп. флаги :
   O_APPEND
   O_CREAT
   O_EXCL
   O_TRUNC
   O_NOCTTY
   O_NONBLOCK
 3 доп. флага для синхронизации работы с файлами :
   O_DSYNC
   O_RSYNC
   O_SYNC
 
 Команда create :
   int creat(const char *pathname, mode_t mode);
 
 Команда close:
   int close(int filedes);
 
 Функция lseek
   off_t lseek(int filedes, off_t offset, int whence);
 Второй аргумент - смещение относительно текущей позиции .
 Аргумент whence может принимать значения :
   SEEK_SET - смещение прибавляется к начальной позиции в файле
   SEEK_CUR - смещение прибавляется к текущей позиции в файле
   SEEK_END - смещение прибавляется к концу файла
 Для регулярных файлов смещение должно быть положительным числом.
 
 Функция read
   ssize_t read(int filedes, void *buf, size_t nbytes);
 Функция возвращает число прочитанных байт.
 
 Функция write
 ssize_t write(int filedes, const void *buf, size_t  nbytes);
 Функция возвращает число записанных байт.
 
 Функция pwrite
   ssize_t pwrite(int filedes, const void *buf,  size_t nbytes, off_t offset);
 Добавляет запись в конец файла без изменения текущей позиции в файле.
 
 

2. Эти два вызова дублируют уже существующий дескриптор :

   int dup(int filedes);
   int dup2(int filedes, int filedes2);
 
 В dup2 мы можем задать во втором аргументе значение нового дескриптора .
 Созданный дескриптор работает точно также , как и копируемый дескриптор ,
 т.е. имеет те же права на тот же файл .
 
 

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

   sync()
 
 Для того , чтобы ядро сбросил кеш только для определенного файла , нужно вызвать
   int fsync(int filedes);
 

4. fcntl меняет атрибуты уже открытого файла

   int fcntl(int filedes, int cmd, ... /* int arg */ );
 
 Функция может применяться для следующих задач :
   1 Дублирование существующего дескриптора : cmd = F_DUPFD
   2 Чтение/Изменение флагов : cmd = F_GETFD или F_SETFD
   3 Чтение/Изменение статус-флагов : cmd = F_GETFL or F_SETFL
   4 Чтение/Изменение владельца (cmd = F_GETOWN or F_SETOWN)
   5 Чтение/Изменение блокировки  (cmd = F_GETLK, F_SETLK, F_SETLKW) 
 
 Статус-флаги :
   _RDONLY   open for reading only
   O_WRONLY  open for writing only
   O_RDWR    open for reading and writing
   O_APPEND  append on each write
   O_NONBLOCK  nonblocking mode
   O_SYNC    wait for writes to complete (data and attributes)
   O_DSYNC   wait for writes to complete (data only)
   O_RSYNC   synchronize reads and writes
   O_FSYNC   wait for writes to complete (FreeBSD and Mac OS X only)
   O_ASYNC   asynchronous I/O (FreeBSD and Mac OS X only)
 
 

5. Функции возвращают статистику о файле

   int stat(const char *restrict pathname, struct stat *restrict buf);
   int fstat(int filedes, struct stat *buf);
 
   В первой функции нужно указать путь к файлу , во второй - дескриптор .
 Вторым параметром является структура stat :
  struct stat {
        mode_t    st_mode;      /* file type & mode (permissions) */
        ino_t     st_ino;       /* i-node number (serial number) */
        dev_t     st_dev;       /* device number (file system) */
        dev_t     st_rdev;      /* device number for special files */
        nlink_t   st_nlink;     /* number of links */
        uid_t     st_uid;       /* user ID of owner */
        gid_t     st_gid;       /* group ID of owner */
        off_t     st_size;      /* size in bytes, for regular files */
        time_t    st_atime;     /* time of last access */
        time_t    st_mtime;     /* time of last modification */
        time_t    st_ctime;     /* time of last file status change */
        blksize_t st_blksize;   /* best I/O block size */
        blkcnt_t  st_blocks;    /* number of disk blocks allocated */
      };
 

6. Это :

   регулярные файлы 
   каталоги 
   блочные и символьные файлы (файлы устройств в dev)
   fifo 
   сокеты
   симлинки 
 
 Тип файла хранится в stat.st_mode 
 Там же хранятся права для владельца , группы и всех остальных.
 
 

6.

   int access(const char *pathname, int mode);
 
 Возвращает 0 в случае успеха  
 Провеяет , имеет ли текущий процесс доступ кфайлу
 
 Параметр mode может принимать значения :
   R_OK  test for read permission
   W_OK  test for write permission
   X_OK  test for execute permission
   F_OK  test for existence of file
 

8. Функция umask используется для установки атрибутов файла при его создании :

    umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
      if (creat("bar", RWRWRW) < 0)
         err_sys("creat error for bar");
   
 В данном случае umask дисэблитвсе разрешения для группы.
 
 Биты функции umask :
   0400  user-read
   0200  user-write
   0100  user-execute
   0040  group-read
   0020  group-write
   0010  group-execute
   0004  other-read
   0002  other-write
   0001  other-execute
 
 Функции chmod и fchmod позволяют изменить атрибуты уже существующего файла :
   int chmod(const char *pathname, mode_t mode);
   int fchmod(int filedes, mode_t mode);
 
 Пример :
    struct stat      statbuf;
      if (stat("foo", &statbuf) < 0) ...
      if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) ...
      if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) ...
 
 Функции  chown и  fchown меняют user id и group id файла :
   int chown(const char *pathname, uid_t owner, gid_t group);
   int fchown(int filedes, uid_t owner, gid_t group);
 
 
 
 

9.

   link   - создание ссылки на каталог
   unlink - удаление файла или каталога
   remove - удаление файла или каталога 
   rmdir  - удаление каталога 
   rename - переименование файла или каталога
   symlink - создание симлинка - отличается от линка тем , что может быть создан
       для любой файловой системы , а не только для текущей
    
 

10.

   DIR *opendir(const char *pathname);
   struct dirent *readdir(DIR *dp);
   void rewinddir(DIR *dp);
   int closedir(DIR *dp);
 
 Структура  dirent :
 
   struct dirent {
         ino_t d_ino;                  /* i-node number */
         char  d_name[NAME_MAX + 1];   /* null-terminated filename */
       }
 
 

11. В отличие от регулярного файла , нет . В директории есть точечная нода , а в симлинке хранится путь .

 
 

12. На глубину нет , но на длину пути есть ограничение - PATH_MAX.

 
 

13. Функция fopen открывает поток и возвращает указатель на обьект FILE. В этой структуре есть файловый дескриптор , указатель на буфер потока , размер буфера , и т.д. Указатель на FILE передается во все функции стандартной библиотеки ввода-вывода , Каждому процессу соответствуют 3 стандартных потока : стандартный ввод , стандартный вывод , и ошибки - соответственно STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO . Указатели на них - stdin, stdout, stderr .

  Буферизация бывает 3-х типов :
   1 Полная - при этом в нужный момент происходит сброс буфера на диск - flush
   2 Построчная
   3 Никакая 
 
 Стандартные ввод и вывод относятся к первому типу.
 В любой момент буфер можно сбросить на диск -
   int fflush(FILE *fp);
 

14. Открытие стандартного потока :

 FILE *fopen(const char *restrict pathname, const char *restrict type);
 FILE *freopen(const char *restrict pathname, const char *restrict type,              FILE *restrict fp);
 FILE *fdopen(int filedes, const char *type);
 
 Получение дескриптора :
   int fileno(FILE *fp);
 
 Создание уникального временного файла
   char *tmpnam(char *ptr);
 
 Закрытие :
   int fclose(FILE *fp);
 
 freopen используется обычно для открытия стандартных ввода и вывода
 Аргумент nype - это r , w , rw и т.д. 
 
  Мы можем читать или писать в следующих вариантах
   1 По-символьно - чтение - getc  , fgetc , запись -  putc , fputc 
   2 По-строчно   - чтение - fgets , запись - fputs
   3 По-обьектно  - чтение - fread , запись - fwrite
 
 Программу копирования стандартного ввода на вывод можно реализовать с помощью первых 2-х
 вариантов :
 
   посимвольно : 
   int     c;
      while ((c = getc(stdin)) != EOF)
          if (putc(c, stdout) == EOF)
              err_sys("output error");
 
 
   построчно :
   char    buf[MAXLINE];
     while (fgets(buf, MAXLINE, stdin) != NULL)
         if (fputs(buf, stdout) == EOF)
             err_sys("output error");
 
  Пример с обьектной бинарной записью :
     struct {
       short   count;
       long    total;
       char    name[NAMESIZE];
     } item;
 
     if (fwrite(&item, sizeof(item), 1, fp) != 1)
         err_sys("fwrite error");
 
 
 
 

15. printf пишет в стандартный вывод, fprintf пишет в файл , sprintf пишет в буфер.

  Типы конвертации :
 d,i signed decimal
 o   unsigned octal
 u   unsigned decimal
 x,X unsigned hexadecimal
 f,F double floating-point number
 e,E double floating-point number in exponential format
 g,G interpreted as f, F, e, or E, depending on value converted
 a,A double floating-point number in hexadecimal exponential format
 c   character (with l length modifier, wide character)
 s   string (with l length modifier, wide character string)
 p   pointer to a void
 n   pointer to a signed integer into which is written the number of characters written so far
 
 scanf - форматированный ввод
 

16.

   int gettimeofday(struct timeval *restrict tp, void  *restrict tzp);
 Эта функция хранит время в структуре
   struct timeval {
            time_t tv_sec;    /* seconds */
            long   tv_usec;   /* microseconds */
       };
 Здесь количество секунд и микросекунд - начиная со стартовой даты 
 
 Две функции получают время в формате структуры tm:
   struct tm *gmtime(const time_t *calptr);
   struct tm *localtime(const time_t *calptr);
 Здесь структура tm -    
 struct tm {        /* a broken-down time */
      int  tm_sec;     /* seconds after the minute: [0 - 60] */
      int  tm_min;     /* minutes after the hour: [0 - 59] */
      int  tm_hour;    /* hours after midnight: [0 - 23] */
      int  tm_mday;    /* day of the month: [1 - 31] */
      int  tm_mon;     /* months since January: [0 - 11] */
      int  tm_year;    /* years since 1900 */
      int  tm_wday;    /* days since Sunday: [0 - 6] */
      int  tm_yday;    /* days since January 1: [0 - 365] */
      int  tm_isdst;   /* daylight saving time flag: <0, 0, >0 */
    };
 
 2 Функции получают время в удобном формате :
   char *asctime(const struct tm *tmptr);
   char *ctime(const time_t *calptr);
 типа :    Tue Feb 10 18:27:38 2004\n\0
 
 Функция strftime позволяет форматировать вывод в стиле printf :
   size_t strftime(char *restrict buf, size_t maxsize,
                 const char *restrict format,
                 const struct tm *restrict tmptr);
 
 
 
 
 

17.

   void *malloc(size_t size);
   void *calloc(size_t nobj, size_t size);
   void *realloc(void *ptr, size_t newsize);
   void free(void *ptr);
 
   malloc выделяет память в байтах . 
   calloc выделяет память для обьектов .
   realloc уменьшает или увеличивает обьем уже выделенной динамической памяти.
   Все 3 функции возвращают указатель 
 
 

18.

   char *getenv(const char *name);
  Возвращает значение переменной по ее имени.
 
   int putenv(char *str);
   Строка name=value помещается в список переменных
 
     int setenv(const char *name, const char *value, int rewrite);
   Для имени устанавливается значение
 
     int unsetenv(const char *name);
    Удаляет переменную по имени .  
 
 Наиблоее употребляемые переменные среды :
   HOME     - домашний каталог
   LANG   - имя локали
   LOGNAME  - логин
   PATH   - пути поиска
   PWD    - текущий каталог
   
 

19. Когда дочерний сигнал заканчивается , ядро посылает родителю сигнал SIGCHLD . Родитель по умолчанию этот сигнал игнорирует . В родителе обычно пишут обработчик этого сигнала , в котором вызывают wait либо waitpid , которые возвращают статус потомка .

   pid_t wait(int *statloc);
   pid_t waitpid(pid_t pid, int *statloc, int options);
 
   Обе возвращают id-шник
 
 Разница между этими 2-мя функциями в том , что первая - блокируемая , а вторую можно задать
 с не-блокируемым параметром . Аргумент statloc возвращает статус потомка.
 Аргумент pid у функции waitpid может принимать следующие значения :
   pid=1 - обрабатываются все дочерние процессы
   pid>1 - обрабатывается конкретный потомок
   pid=0 - обрабатываются все дочерние процессы , у которых group id равен group id родителя
 Аргумент options может быть WNOHANG - в этом случае waitpid становится неблокируемой  
 

20. Имя сигнала начинается с SIG . У сигналов могут быть разные причины и источники :

   1 Генерятся терминалом - например , Ctrl+C - SIGINT
   2 Hardvare exceptions - invalid memory reference - SIGSEGV
   3 kill - функция посылает сигнал другому процессу
   4 Software conditions - например SIGPIPE - попытка записать в закрытый канал 
 
  Ядро в ответ на сигнал может вести себя так :
   1 Игнорировать сигнал : все сигналы могут быть проигнорированы , кроме SIGKILL и SIGSTOP.
   2 Обработать сигнал
   3 Вызвать акцию по умолчанию : для большинства сигналов это конец процесса .
 
  Системный вызов signal :
   void *signal(int signo, void (*func)(int)))(int);
 Первый аргумент - номер сигнала , второй может быть :
   SIG_IGN
   SIG_DFL
   функция
 
 
   
     
 

21. Функция kill посылает сигнал другому процессу , raise - самому себе .

   int kill(pid_t pid, int signo);
   int raise(int signo);
 
  Поведение kill зависит от pid
   1 pid > 0 - сигнал посылается процессу с данным pid
   2 pid = 0 - посылается всем процессам , у которых group id равен его group id
   3 pid < 0 - посылается всем процессам , у которых group id равен модулю pid
   4 pid = 1 - посылается всем процессам , к которым имеет разрешение на сигнал
 
 root может посылать сигнал любому процессу
 signo = 0 - такой сигнал обычно посылается для проверки , жив ли процесс
 
 Функции alarm и pause используются для того . чтобы перевести процесс в спящий режим .
 
 

22. Функция позволяет обработать сигнал

 int sigaction(int signo, 
         const struct sigaction *restrict act,
         struct sigaction *restrict oact);
 
 signo - номер сигнала
 
 Функция использует структуру
   struct sigaction {
        void      (*sa_handler)(int);   /* addr of signal handler, */
                                        /* or SIG_IGN, or SIG_DFL */
        sigset_t sa_mask;               /* additional signals to block */
        int      sa_flags;              /* signal options, Figure 10.16 */
 
        /* alternate handler */
        void     (*sa_sigaction)(int, siginfo_t *, void *);
     };
 
 первое поле - обработчик-функция 
 второе - обработка маски сигнала
 
 поле sa_flags принимает различные значения , например :
 SA_NOCLDSTOP - при выходе из дочернего процесса НЕ генерится SIGCHLD
 
 

23.

 Сравнение тредов :
   int pthread_equal(pthread_t tid1, pthread_t tid2);
 
 Получение id :
   pthread_t pthread_self(void);
 
 Создание треда :
 int pthread_create(pthread_t *restrict tidp,
                    const pthread_attr_t *restrict attr,
                    void *(*start_rtn)(void), 
        void *restrict arg);
 
 Окончание треда внутря самого треда :
   void pthread_exit(void *rval_ptr);
 
 Раз-аттачивание потока :
   int pthread_join(pthread_t thread, void **rval_ptr);
 
 Один тред может остановить другой :
   int pthread_cancel(pthread_t tid);
 
 

24. Мьютекс - блокировка , которую мы ставим , когда получаем доступ к ресурсу , и освобождаеи ее , когда освобождаем ресурс . Инициализация мьютекса :

 int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                        const pthread_mutexattr_t *restrict attr);
 Для инициализации мьютекса по умолчанию , второй атрибут = NULL
 
 Удаление мьютекса :
   int pthread_mutex_destroy(pthread_mutex_t *mutex);
   
 Блокировка :
   int pthread_mutex_lock(pthread_mutex_t *mutex);
   int pthread_mutex_unlock(pthread_mutex_t *mutex);
 

25. В отличие от мьютекса , у этого типа блокировки не два , а три состояния :

   1 блокировка на чтение
   2 блокировка на запись
   3 разблокирование
 
 Блокировку на запись может делать только один поток , на чтение - несколько.
 
 Инициализация :
   int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
               const pthread_rwlockattr_t *restrict attr);
   int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
 
 
 

26. В комбинации с мьютексом они делают уведомление тредам о том , что мьютекс разблокирован , и можно пользоваться заблокированным ресурсом .

 Инициализация :
   int pthread_cond_init(pthread_cond_t *restrict cond,
             pthread_condattr_t *restrict attr);
   int pthread_cond_destroy(pthread_cond_t *cond);
 
 Ожидание :
 int pthread_cond_wait(pthread_cond_t *restrict cond,
                       pthread_mutex_t *restrict mutex);
 
 Уведомление :
   int pthread_cond_signal(pthread_cond_t *cond);
   int pthread_cond_broadcast(pthread_cond_t *cond);
 
 

27. Атрибуты треда можно изменить с помощью функций :

   int pthread_attr_init(pthread_attr_t *attr);
   int pthread_attr_destroy(pthread_attr_t   *attr);
   int pthread_attr_setdetachstate(pthread_attr_t
       *attr, int detachstate);
 
   pthread_attr_t - структура атрибутов треда
   
 Для изменения размера стека , отводимого под треды , есть функция
   int pthread_attr_setstack(const pthread_attr_t *attr,
                           void *stackaddr, size_t *stacksize);
 
 Атрибуты есть у мьютексов - по типу атрибутов мьютексы можно разделить на 4 группы :
   PTHREAD_MUTEX_NORMAL
   PTHREAD_MUTEX_ERRORCHECK
   PTHREAD_MUTEX_RECURSIVE
   PTHREAD_MUTEX_DEFAULT 
 
 
 

2.

 
 

2.

 
 

1.

 Открыть файло для неблокирующего чтения :
   myfd = open("/home/ann/my.dat", O_RDONLY | O_NONBLOCK);
 
 Создание файла :
   int fd = open("info.dat", O_RDWR | O_CREAT, fdmode)
 
 
 
 Чтение :
    ssize_t read(int fildes, void *buf, size_t nbyte);
 
 Пример : прочитать 100 байт из стандартного ввода :
   char buf[100];
   bytesread = read(STDIN_FILENO, buf, 100);
 
 
 Запись :
   ssize_t write(int fildes, const void *buf, size_t nbyte);
 
 
 
 Пример : функция читает из одного дескриптора и пишет в другой :
 int readwrite(int fromfd, int tofd) 
 {
    char buf[BLKSIZE];
    int bytesread;
 
    if ((bytesread = r_read(fromfd, buf, BLKSIZE)) == -1)      return -1;
    if (r_write(tofd, buf, bytesread) == -1)      return -1;
    return bytesread;
 }
 
 
 

2.

  Функция select дает возможность управления массивом дескрипторов :
    int select(int nfds, 
         fd_set *restrict readfds,
         fd_set *restrict writefds, 
         fd_set *restrict errorfds,
         struct timeval *restrict timeout);
 Первый аргумент nfds - это диапазон дескрипторов.
 Второй параметр - дескрипторы на чтение , третий на запись .
 Последний параметр форсирует возвращение из select по истечение времени, если он=NULL,
 селект по идее подвисает .
 При успешном возвращении функция возвращает число дескрипторов .
 
 Макросы для работы с битами дескрипторов :
 void FD_CLR(int fd, fd_set *fdset); - очищает указываемые биты
 int FD_ISSET(int fd, fd_set *fdset); - проверяет биты
 void FD_SET(int fd, fd_set *fdset); - устанавливает биты
 void FD_ZERO(fd_set *fdset); - очищает все биты
 
 Пример : Функция возвращает один из 2-х дескрипторов :
 int whichisready(int fd1, int fd2) 
 {
    int maxfd;
    int nfds;
    fd_set readset;
 
    maxfd = (fd1 > fd2) ? fd1 : fd2;
    FD_ZERO(&readset);
    FD_SET(fd1, &readset);
    FD_SET(fd2, &readset);
    nfds = select(maxfd+1, &readset, NULL, NULL, NULL);
    if (FD_ISSET(fd1, &readset))    return fd1;
    if (FD_ISSET(fd2, &readset))    return fd2;
    return -1;
 }
 
 Пример : чтение из нескольких файлов с помощью массива дескрипторов :
 void monitorselect(int fd[], int numfds) {
    char buf[BUFSIZE];
    int bytesread;
    int i;
    int maxfd;
    int numnow, numready;
    fd_set readset;
 
    maxfd = 0;                  /* установка диапазона дескрипторов */
    for (i = 0; i < numfds; i++) 
    {
        if (fd[i] >= maxfd)
           maxfd = fd[i] + 1;
    }
    numnow = numfds;
    
   while (numnow > 0) 
   {            /* continue monitoring until all are done */
       FD_ZERO(&readset);                  /* set up the file descriptor mask */
         for (i = 0; i < numfds; i++)
     { 
          if (fd[i] >= 0)
             FD_SET(fd[i], &readset);
     }
 
       numready = select(maxfd, &readset, NULL, NULL, NULL);  /* which ready? */
 
    for (i = 0; (i < numfds) && (numready > 0); i++) 
     { /* read and process */
       if (FD_ISSET(fd[i], &readset)) {        /* this descriptor is ready */
         bytesread = r_read(fd[i], buf, BUFSIZE);
         numready--;
         if (bytesread > 0)
           docommand(buf, bytesread);
         else  {           /* error occurred on this descriptor, close it */
           r_close(fd[i]);
           fd[i] = -1;
           numnow--;
             }
          }
       }
    }
 
    for (i = 0; i < numfds; i++)
        if (fd[i] >= 0)
            r_close(fd[i]);
 }
 
 
 
 
 

3.

   int poll(struct pollfd fds[], nfds_t nfds, int timeout);
 
 Это аналог select , возвращает число дескрипторов .
 Select отличается от poll тем , что модифицирует флаги дескрипторов ,
 поэтому при каждом вызове select  в цикле надо сбрасывать эти флаги .
 В poll нет maxfd .
 Второй параметр - число дескрипторов .
 Первый параметр - структура , в которой есть члены :
   int fd;         /* file descriptor */
   short events;   /* requested events */
   short revents;  /* returned events */
 
 Аналогичный пример для poll :
 void monitorpoll(int fd[], int numfds)  {
    char buf[BUFSIZE];
    int bytesread;
    int i;
    int numnow = 0;
    int numready;
    struct pollfd *pollfd;
 
    for (i=0; i< numfds; i++)             /* initialize the polling structure */
       if (fd[i] >= 0)
           numnow++;
    if ((pollfd = (void *)calloc(numfds, sizeof(struct pollfd))) == NULL)
       return;
    for (i = 0; i < numfds; i++) {
       (pollfd + i)->fd = *(fd + i);
       (pollfd + i)->events = POLLRDNORM;
    }
  
   while (numnow > 0) {        /* Continue monitoring until descriptors done */
       numready = poll(pollfd, numfds, -1);
       if ((numready == -1) && (errno == EINTR))
          continue;                /* poll interrupted by a signal, try again */
       else if (numready == -1)            /* real poll error, can't continue */
          break;
       for (i = 0; i < numfds && numready > 0; i++)  {
          if ((pollfd + i)->revents) {
             if ((pollfd + i)->revents & (POLLRDNORM | POLLIN) ) {
                bytesread = r_read(fd[i], buf, BUFSIZE);
                numready--;
                if (bytesread > 0)
                   docommand(buf, bytesread);
                else
                   bytesread = -1;                             /* end of file */
             } else if ((pollfd + i)->revents & (POLLERR | POLLHUP))
                bytesread = -1;
             else                    /* descriptor not involved in this round */
                bytesread = 0;
             if (bytesread == -1) {      /* error occurred, remove descriptor */
                r_close(fd[i]);
                (pollfd + i)->fd = -1;
                numnow--;
             }
          }
       }
    }
    for (i = 0; i < numfds; i++)
        r_close(fd[i]);
    free(pollf
 }
 
 
 

4. Стандартная библиотека I/O (fopen, fscanf, fprintf, fread, fwrite, fclose and so on) использует файловые указатели. Юниксовая I/O (open, read, write, close and ioctl) использует файловые дескрипторы. Надо понимать , что первая лежит в отдельной библиотеке , а вторая зашита в ядро.

  Организация дескрипторов в юниксе сделана на 3-х уровнях :
   1 каждый процесс имеет свою таблицу дескрипторов
   2 ядро имеет глобальную таблицу дескрипторов , в котором для каждого дескриптора
     указываются процессы , которые работают с ним
   3 имеется список нод в файловой системе
 
 Поэтому , в обычных условиях , когде не делается блокировка , 
 2 разных процесса , которые открывают один и тот же файл на чтение и на запись ,
 могут получить разные результаты , поскольку ядро кеширует файлы .
 
 Пример : записать строку в файл с помощью стандартной И/О :
 FILE *myfp;
 if ((myfp = fopen("/home/ann/my.dat", "w")) == NULL)
    perror("Failed to open /home/ann/my.dat");
 else
    fprintf(myfp, "This is a test");
 
 Приемер : каков порядок вывода ?
    fprintf(stdout, "a");
    fprintf(stderr, "a has been written\n");
    fprintf(stdout, "b");
    fprintf(stderr, "b has been written\n");
    fprintf(stdout, "\n");
 
 Сначала будет выведен stderr , потому что он не буферизуется , а потом stdout .
 
 Пример : каков порядок вывода ?
    fprintf(stdout, "a");
    scanf("%d", &i);
    fprintf(stderr, "a has been written\n");
    fprintf(stdout, "b");
    fprintf(stderr, "b has been written\n");
    fprintf(stdout, "\n");
  
 функция scanf пишет буфер на диск , поэтому сначала будет выведено "a"
 Далее :
     "a has been written\n"
     "b has been written\n"
     "b";
 
 Пример : что напечатает программа ?
 int main(void) {
    printf("This is my output.");
    fork();
    return 0;
 }
 будет выведена 1 строка : This is my output.This is my output.
 Поскольку стандартный вывод наследуется потомком , вывод сплюсуется в буффере сам с собой 2 раза ,
 
 
 
 

5.

   int dup2(int fildes, int fildes2);
 
 Закрывает второй дескриптор и копирует в него первый
 Возвращает скопированный дескриптор в случае успеха .
 
 Пример : перенаправление стандартного вывода в файл :
 
   int fd = open("my.file", CREATE_FLAGS, CREATE_MODE);
   int fd2 = dup2(fd, STDOUT_FILENO);
   close(fd);
   write(STDOUT_FILENO, "OK", 2);
 
 
 

6. Функция модифицирует флаги дескриптора :

    int fcntl(int fildes, int cmd, /* arg */ ...);
 
  Часто используется для установки не-блокируемого ввода-вывода в контенте использования
 двумя другими функциями select и poll . Для этого нужно использовать функцию open с флагом O_NONBLOCK.
 То же самое можно сделать для уже открытого дескриптора с помощью fcntl ,
 для этого команда вызывается дважды - на чтение-запись :
 Пример :
    int fdflags = fcntl(fd, F_GETFL, 0);
    fdflags |= O_NONBLOCK;
    fcntl(fd, F_SETFL, fdflags);
 
  
 
 

7.

 Изменение текущей директории:
   int chdir(const char *path);
 
 Получение текущей директории:
  char *getcwd(char *buf, size_t size);
 
 Открыть директорию :
  DIR *opendir(const char *dirname);
 
 Прочитать директорию :
   struct dirent *readdir(DIR *dirp);
 
 Закрыть директорию :
   int closedir(DIR *dirp);
 
 Пример : распечатать текущий каталог
   DIR * dirp = opendir("sdfsdf");
   
  Создать хард-линк :
    int link(const char *path1, const char *path2);
 Удалить хард-линк :
    int unlink(const char *path);
 
 Создать симлинк
   int symlink(const char *path1, const char *path2);
 
  Хард-линк - это разные линки на один и тот же файл.
 Хард-линки - это разные копии одного файла .
 
 
 Команда which - выводит путь до исполняемого файла , например :
   which mc
   /usr/bin/mc
 
 Функция проверяет , является ли файл исполняемым :
   int checkexecutable(char *name);
 
 

8. Команда stty позволяет выводить или устанавливать настройки терминала :

   stty -a - вывод
   stty -g - установка
 
 Эта информация хранится в структуре termios , которая хранит флаги и последовательность управляющих символов .
 
 Получение атрибутов
   int tcgetattr(int fildes, struct termios *termios_p);
 Установка атрибутов
   int tcsetattr(int fildes, int optional_actions,const struct termios *termios_p);
 
 Пример : функция устанавливает или отменяет ехо-символ , т.е. перенаправление символа ,
 набираемого в стандартном вводе , на стандартный вывод :
 
 int setecho(int fd, int onflag) 
 {
    int error;
    struct termios term;
 
    tcgetattr(fd, &term);
    if (onflag)                                        /* turn echo on */
       term.c_lflag |= ECHOFLAGS;
    else                                              /* turn echo off */
       term.c_lflag &= ~ECHOFLAGS;
    while (((error = tcsetattr(fd, TCSAFLUSH, &term)) == -1) &&
            (errno == EINTR)) ;
    return error;
 }
 
 
 У терминала может быть 2 режима - канонический и не-канонический . Первый - это обычный , 
 когда мы можем набирать длинную строку и завершать ее ентером . 
 Неканонический - это посимвольный ввод .
 
 
 
 

9. Для обработки массива файловых дескрипторов возможны несколько основных подходов :

   1 Один процесс обрабатывает один дескриптор
   2 select
   3 poll
   4 Неблокирующий I/O + поллинг
   5 Асинхронный I/O
   6 Потоковая обработка дескрипторов
 
  1-й вариант работает в случае независимой обработки дескрипторов , когда форкаются дочерние процессы ,
 каждый из которых обрабатывает свой дескриптор.
  2-й и 3-й варианты используют блокирующую обработку 
  4-й подход используется там , где программа помимо обработки должна вовсю использовать
 ресурсы процессора для решения других задач , этот подход труден с точки зрения реализации .
  5-й подход основан на обработке I/O сигналов и использовании асинхронных функций ,
 в этом подходе основная проблема - это доступ к общим ресурсам , дэдлки и race condition ,
 этот подход также труден с точки зрения реализации .
  6-й подход лишен этих проблем в реализации . Но много-поточность не используется широко ,
 потому что такие программы не-портабельны .
 
 
 
 
 

10.

   Получение собственного id :
    pthread_t pthread_self(void);
  Сравнение тредов :
   pthread_t pthread_equal(thread_t t1, pthread_t t2);
  Создание треда :
   int pthread_create(pthread_t *restrict thread,
                      const pthread_attr_t *restrict attr,
                      void *(*start_routine)(void *), 
            void *restrict arg);
 Тут 4-й параметр - это аргумент,передаваемый в тредовую функцию , это может быть простой тип ,
 массив , структура .
 
 Пример : в тредовой функции читается массив из 2-х деструкторов , переданных в нее в качестве параметра :
 void *copyfilemalloc(void *arg)  
 { /* copy infd to outfd with return value */
    int infd;
    int outfd;
 
    infd = *((int *)(arg));
    outfd = *((int *)(arg) + 1);
    r_close(infd);
    r_close(outfd);
 }
 
  Сделать тред отсоединенным :
    int pthread_detach(pthread_t thread);
  Освобождение ресурсов присоединенного треда :
   int pthread_join(pthread_t thread, void **value_ptr);
  Тред может терминировать сам себя:
   void pthread_exit(void *value_ptr);
  Тред может терминировать другой тред , если у того PTHREAD_CANCEL_DISABLE :
   int pthread_cancel(pthread_t thread);
  Тред может изменить свой статус    
   int pthread_setcancelstate(int state, int *oldstate);
   int pthread_setcanceltype(int type, int *oldtype);
     void pthread_testcancel(void);
 
 В случае , когда тред возвращает какое-то значение , лучше всего для этого выделять дополнительную
 память , куда помещать возвращаемое значение , и указатель на эту память , а еще лучше 
 поместить этот указатель в качестве 4-го параметра функции  pthread_create.
 
 Пример : дана тредовая функция 
 void *whichexit(void *arg) {
    int n;
    int np1[1];
    int *np2;
    char s1[10];
    char s2[] = "I am done";
    n = 3;
    np1[0] = n;
    np2 = (int *)malloc(sizeof(int *));
    *np2 = n;
    strcpy(s1, "Done");
    return(NULL);
 }
 
 Что можно вернуть вместо NULL ?
    1. n   - нет
    2. &n  - нет
    3. (int *)n  - да
    4. np1 - нет
    5. np2 - да
    6. s1  - нет
    7. s2  - нет
    8. "This works" - да 
    9. strerror(EINTR) - нет
 
 
 Функции стандартной библиотеки С , которые могут вызываться из тредовых функций , разбиты на 2 категории :
   1 trade-safe
   2 non trade-safe
 
 

11. Функции , которые управляют атрибутами тредов , можно разделить на 4 группы :

   1 Управление атрибутами :
     int pthread_attr_destroy(pthread_attr_t *attr);
     int pthread_attr_init(pthread_attr_t *attr);
   2 Управление состоянием :
     pthread_attr_getdetachstate
     pthread_attr_setdetachstate
     Установка PTHREAD_CREATE_JOINABLE и PTHREAD_CREATE_DETACHED.
   3 Управление стеком :
     pthread_attr_getguardsize
     pthread_attr_setguardsize
     pthread_attr_getstack
     pthread_attr_setstack
   4 scheduling
     pthread_attr_getinheritsched
     pthread_attr_setinheritsched
     pthread_attr_getschedparam
     pthread_attr_setschedparam
     pthread_attr_getschedpolicy
     pthread_attr_setschedpolicy
     pthread_attr_getscope
     pthread_attr_setscope   
     Установка PTHREAD_INHERIT_SCHED и PTHREAD_EXPLICIT_SCHED. 
 
 Пример : создание приоритета для треда :
    int priority = 10 ; 
    pthread_attr_t *attr;
    int error;
    struct sched_param param;
 
    attr = (pthread_attr_t *)malloc(sizeof(pthread_attr_t));
    pthread_attr_init(attr);
    pthread_attr_getschedparam(attr, ¶m);
    param.sched_priority = priority;
    pthread_attr_setschedparam(attr, ¶m);
 
 
 

12. Мьютексы - простой и эффективный механизм синхронизации тредов .

   Структура pthread_mutex_t  - обьект мьютекса
 Проинициализировать мьютекс можно статически с помощью PTHREAD_MUTEX_INITIALIZER
 либо динамически с помощью pthread_mutex_init :
  int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                           const pthread_mutexattr_t *restrict attr);
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 
 Удаление мьютекса :
   int pthread_mutex_destroy(pthread_mutex_t *mutex);
 
 Блокировка и разблокировка мьютекса :
   int pthread_mutex_lock(pthread_mutex_t *mutex);
   int pthread_mutex_trylock(pthread_mutex_t *mutex);
   int pthread_mutex_unlock(pthread_mutex_t *mutex);
 
 Первая вернет управление лишь тогда , когда получит мьютекс , вторая возвращает немедленно .
 
 Пример создания критической секции :
   pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
   pthread_mutex_lock(&mylock);
   ...
   pthread_mutex_unlock(&mylock);
 
 Мьютекс можно использовать для создания критической секции , которая является оберткой 
 для стандартных НЕ thread-safe функция типа rand .
 
   
 

13.

  Рассмотрим пример , когда внутри треда есть бесконечный цикл :
   while (x != y) ;
 Если переменные нигде не меняются ни в каких тредах , данный тред может просто заблокировать все остальные треды .
 Решение заключается в том , чтобы выполнить следующую цепочку действий :
   1 Заблокировать мьютекс
   2 Проверить while (x != y) ;
   3 Если да , то нет проблем - просто разблокируем мьютекс
   4 Если нет - то переводим стред в спящее состояние 
   5 По выходу из спячки разблокирываем мьютекс .
 Здесь нам понадобится новый тип данных - condition variable .
 Наш пример теперь будет выглядеть так :
   pthread_mutex_lock(&m);
   while (x != y)
   pthread_cond_wait(&v, &m);
   pthread_mutex_unlock(&m);
 
 pthread_cond_wait может быть вызвана только в том треде , который является собственником мьютекса .
 
 
 Другая функция - pthread_cond_signal - может извещать другие треды о том , 
 что произошли какие-то изменения :
   pthread_mutex_lock(&m);
   x++;
   pthread_cond_signal(&v);
   pthread_mutex_unlock(&m);
 
 Создание условной переменной :
   int pthread_cond_init(pthread_cond_t *restrict cond,
                         const pthread_condattr_t *restrict attr);
   pthread_cont_t cond = PTHREAD_COND_INITIALIZER;
 
 И удаление :
   int pthread_cond_destroy(pthread_cond_t *cond);
 
 Разновидности :
  int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                         pthread_mutex_t *restrict mutex,
                         const struct timespec *restrict abstime);
   int pthread_cond_wait(pthread_cond_t *restrict cond,
                         pthread_mutex_t *restrict mutex);
 Тут у первой функции есть дополнительный параметр - время ожидания .
 
 Разблокирование всех тредов , которые зашиты на cond :
   int pthread_cond_broadcast(pthread_cond_t *cond);
 
 Рассмотрим пример :
 static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
 static pthread_cond_t v = PTHREAD_COND_INITIALIZER;
 
 pthread_mutex_lock(&m);
 while (!test_condition())                      /* get resource */
    pthread_cond_wait(&v, &m);
     /* do critical section, possibly changing test_condition() */
 pthread_cond_signal(&v);          /* inform another thread */
 pthread_mutex_unlock(&m);
 
 Вызов  pthread_cond_wait(&v, &m) позволит другим тредам получить доступ к этой критической секции.
   
 
 
 
  
 

14. Сигналы могут обрабатываться тредами трояким образом :

   1 Асинхронно - обрабатывается тредом , который в данный момент разблокирован
   2 Синхронно  - обрабатывается вызванным тредом
   3 Напрямую  - вызывается тредом с помощью pthread_kill
           например : pthread_kill(pthread_self(), SIGKILL)
 У каждого треда может быть своя собственная маска , которая устанавливается с помощью
 функции pthread_sigmask
 
 Функция pthread_sigmask настраивает блокировку для различных сигналов .
   
 Для много-поточных приложений рекомендуется создавать отдельный поток для обработки сигналов .
 
 Из всех сигналов только SIGFPE (floating-point exception) является синхронным ,
 все остальные асинхронные .
 
 

15.

 
  Блокировка на чтение-запись имеет тип pthread_rwlock_t.
 Она устанавливается с помощью pthread_rwlock_init.
 
 Блокировка только на чтение :
   pthread_rwlock_rdlock 
   Функция - pthread_rwlock_tryrdlock
 
 Блокировка только на запись :
   pthread_rwlock_wrlock 
   Функция pthread_rwlock_trywrlock
 
 
 

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

 
 

17. Семафор - это целочисленная переменная , которая имеет 2 атомарных операции - wait(lock) и signal(unlock). Если значение семафора больше нуля , wait уменьшает его на единицу . Если значение семафора равно нулю , wait делает блокировку , а signal наоборот разблокирует. Если нет заблокированных семафоров , signal увеличивает семафор на единицу . Следующая условная схема показывает условную реализацию семафоров :

 void wait(semaphore_t *sp) 
 {
    if (sp->value > 0)
       sp->value--;
    else {
       < добавляем процесс в sp->list>
       < блокируем>
    }
 }
 
 void signal(semaphore_t *sp) 
 {
    if (sp->list != NULL)
       < удвляем процесс из sp->list и помечаем его как готовый>
    else
       sp->value++;
 }
 
 Следующий псевдо-код защищает критическую секцию , если значение семафора = 1
   wait(&S);                                
   < critical section>
   signal(&S);                                             
   < remainder section>
 
 Семафор - это обьект типа sem_t. Семафоры бывают именованные и неименованные .
 Инициализация семафора :
   int sem_init(sem_t *sem, int pshared, unsigned value);
 pshared устанавливает , может ли семафор использоваться другими процессами.
 value обычно=1, указывая на то , что первый процесс может делать блокировку с помощью данного семафора
 
 Пример : создание семафора , который может быть использован только тредами данного процесса :
   sem_t semA;
   sem_init(&semA, 0, 1)
 
 sem_post - это реализация выше-описанной signal . Если нет тредов , заблокированных семафором ,
 происходит инкремент семафора . Если тред заблокирован семафором , семафор обнуляется .
 
 sem_wait - если семафор=0 , то тред блокируется до тех пор , пока не будет вызвана sem_post .
 
 Пример : защита расшаренной переменной с помощью семафора
 3 функции - initshared , getshared и incshared - соответственно инициализируют , получают и
 увеличивают эту переменную . Переменная называется shared .
 
 static int shared = 0;
 static sem_t sharedsem;
 
 int initshared(int val) {
     if (sem_init(&sharedsem, 0, 1) == -1)
         return -1;
     shared = val;
     return 0;
 }
 
 int getshared(int *sval) {
     while (sem_wait(&sharedsem) == -1)
         if (errno != EINTR)
             return -1;
     *sval = shared;
     return sem_post(&sharedsem);
 }
 
 int incshared() {
     while (sem_wait(&sharedsem) == -1)
         if (errno != EINTR)
             return -1;
     shared++;
     return sem_post(&sharedsem);
 }
 
 Пример : создается семафор , который передается в качестве параметра в массив из 10 тредов
 
 sem_t semlock;
 pthread_t *tids;
 tids = (pthread_t *)calloc(10, sizeof(pthread_t));
 sem_init(&semlock, 0, 1);
 for (i = 0; i < 10; i++)
     pthread_create(tids + i, NULL, thread_func, &semlock);
 
 

18. Именованный семафор может быть использован различными процессами . Имя его должно начинаться со слеша .

 Установить коннект с семафором :
   sem_t *sem_open(const char *name, int oflag, ...);
 
 Пример : функция получения именованного семафора , либо его создания , если его нет :
 
 #define PERMS (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
 #define FLAGS (O_CREAT | O_EXCL)
 
 int getnamed(char *name, sem_t **sem, int val) 
 {
     while (((*sem = sem_open(name, FLAGS, PERMS, val)) == SEM_FAILED) && (errno == EINTR)) ;
     if (*sem != SEM_FAILED)      return 0;
 
     while (((*sem = sem_open(name, 0)) == SEM_FAILED) && (errno == EINTR)) ;
     if (*sem != SEM_FAILED)     return 0;
 }
 
 Пример : массив форкнутых дочерних процессов , который работает с критической секций ,
 защищенной именованным семафором :
 
 int getnamed(char *name, sem_t **sem, int val);
 
 int main  (int argc, char *argv[]) {
     char buffer[BUFSIZE];
     char *c;
     pid_t childpid = 0;
     int delay;
     volatile int dummy = 0;
     int i, n;
     sem_t *semlockp;
 
     for (i = 1; i < 10; i++)
         if (childpid = fork())          break;
 
     getnamed("/My_Semaphor", &semlockp, 1);
 
     while (sem_wait(semlockp) == -1)                         /* entry section */
 
     while (*c != '\0') 
   {                                  /* critical section */
         fputc(*c, stderr);
         c++;
         for (i = 0; i < delay; i++)
             dummy++;
     }
     if (sem_post(semlockp) == -1) {                           
     /* exit section */
     }
 }
 
 
   
   
 

1. для получения списка переменных окружения нужно прочитать /proc/self/environ и вывести его на экран . В этом файле строки разделены нулями , а не символами перевода строки.

 #include < stdio.h>
 
 #define BUF_SIZE 0x100
 
 int main(int argc, char * argv[])
 {
   char buf[BUF_SIZE];
   int len, i;
   FILE * f; 
   f = fopen("/proc/self/environ", "r");
   while((len = fread(buf, 1, BUF_SIZE-1, f)) > 0)
   {
     for (i = 0; i < len; i++) if (buf[i]==0) buf[i] = 10;
     buf[len] = 0;
     printf("%s", buf);
   }
   fclose(f);
   return 0;
 }
 
 
 
 

2.

 #include < stdio.h>
 #include < dirent.h>
 int sel(struct dirent * d)
 { return 1; // всегда подтверждаем
 }
 int main(int argc, char ** argv) 
 {
   int i, n;
   struct dirent ** entry;
   if (argc != 2)
   {
     printf("Использование: %s < директория>\n", argv[0]);
     return 0;
   }
   n = scandir(argv[1], &entry, sel, alphasort);
   if (n < 0)
   {
     printf("Ошибка чтения директории\n");
     return 1;
   }
   for (i = 0; i < n; i++)
     printf("%s inode=%i\n", entry[i]->d_name, entry[i]->d_ino);
   return 0;
 }
 
 Функция scandir() создает список элементов указанной директории. Ей
 необходимо передать указатель на функцию обратного вызова, которая, получая
 данные об очередном элементе, принимает решение, включать этот элемент в
 результирующий список. В нашем примере это функция sel(). Если при очередном
 вызове функция sel() вернет значение 0, соответствующий элемент директории не
 будет включен в конечный список. Последний параметр scandir - функция
 сортировки элементов директории. Мы используем функцию alphasort(),
 сортирующую элементы в лексикографическом порядке.
  Данные об элементах директории передаются в структурах dirent. 
 Кроме имени файла dirent содержит номер inode для этого элемента 
 (простым программам обычно не зачем знать номера
 inode, но, чтобы наш пример чем-то отличался от стандартного, мы включаем эту
 информацию). 
 
 

3.

 #include < stdio.h>
 #include < dirent.h>
 int main(int argc, char ** argv)
 {
   DIR * d;
   struct dirent * entry;
   if (argc != 2)
   {
     printf("Использование: %s <директория>\n", argv[0]);
     return 0;
   }
   d = opendir(argv[1]);
   if (d == NULL)
   {
     printf("Ошибка чтения директории\n");
     return 1;
   }
   while (entry = readdir(d))
     printf("%s inode=%i\n", entry->d_name, entry->d_ino);
   closedir(d);
   return 0;
 }
 
 

4. это такой файл , фактический размер которого больше реального

 #include < stdio.h>
 #include < string.h>
 #define BIG_SIZE 0x1000000
 int main(int argc, char * argv[])
 { 
   FILE *f;
   f = fopen(argv[1], "w");
   if (f == NULL)
   {
     printf("Невозможно создать файл: %s", argv[1]);
     return 1;
   }
   fwrite(argv[1], 1, strlen(argv[1]), f);
   fseek(f, BIG_SIZE, SEEK_CUR);
   fwrite(argv[1], 1, strlen(argv[1]), f);
   fclose(f);
 }
 
 Если скомпилировать эту программу под именем makehole и запустить
   makehole bighole.txt
 то на диске будет создан файл bighole.txt. Команда ls –al сообщит нам, что
 размер файла составляет чуть больше 16 мегабайт (см. значение константы
 BIG_SIZE в программе). Однако, с помощью команды
   du bighole.txt
 мы узнаем, что на диске этот файл занимает 24 байта. Причиной появления
 пропусков в открытом для записи файле стало смещение с помощью функции
 fseek() в область после конца файла. Выход за пределы файла с помощью fseek() –
 стандартный метод получения разреженных файлов. В момент вызова fseek() в
 нашей программе позиция записи находится в конце файла. Флаг SEEK_CUR
 указывает, что смещение отсчитывается от текущей позиции. Таким образом, в
 файле образуется пропуск, величина которого в байтах соответствует значению
 BIG_SIZE. При чтении пустых блоков в разреженном файле функция чтения
 данных будет возвращать блоки, заполненные нулями.
 
 

5. Блокировка областей файла позволяет нескольким программам совместно работать с содержимым одного и того же файла, не мешая друг другу, или, точнее, мешая друг другу испортить данные. Мы рассмотрим интерфейс блокировки областей, основанный на использовании функции fcntl(2).

 Нужно собрать и запустить несколько экземпляров следующей программы. 
 Первый экземпляр testlocks создаст файл testlocks.txt.
 Каждый процесс заблокирует 64 байта в этом файле и сделает запись в
 заблокированную область. Второй, третий и все последующие экземпляры
 процессов сообщат, какие области файла уже заблокированы другими процессами.
 Завершить программу testlocks можно, нажав любую символьную клавишу и,
 затем, ввод.
 
 #include < stdio.h>
 #include < unistd.h>
 #include < fcntl.h>
 #include < string.h>
 
 int main(int argc, char * argv[])
 {
   int fd; 
   char str[64];
   memset(str, 32, 64);
   struct flock fi;
   int off;
   sprintf(str, "Запись сделана процессом %i", getpid());
   fd = open("testlocks.txt", O_RDWR|O_CREAT);
   fi.l_type = F_WRLCK;
   fi.l_whence = SEEK_SET;
   fi.l_start = 0;
   fi.l_len = 64;
     off = 0;
   while (fcntl(fd, F_SETLK, &fi) == -1)
   {
      fcntl(fd, F_GETLK, &fi);
      printf("байты %i - %i заблокированы процессом %i\n", off, off+64, fi.l_pid);
      off += 64;
      fi.l_start = off;
   }
   lseek(fd, off, SEEK_SET);
   write(fd, str, strlen(str));
   getchar();
   fi.l_type = F_UNLCK;
   if (fcntl(fd, F_SETLK, &fi) == -1)
     printf("Ошибка разблокирования\n");
   close(fd);
 } 
 
 В структуру flock записывается информация о блокировке следующего типа
 
 Поле     Значение
 l_type   Тип блокировки: записи – F_RDLCK, чтения – F_WRLCK, сброс – F_UNLCK.
 l_whence Точка отсчета смещения
 l_start  Начальный байт области
 l_len    Длина области
 l_pid    Идентификатор процесса, установившего
 
 

6. Чаще всего внутри-программные каналы использовуются тогда, когда программа запускает другую программу и считывает данные, которые та выводит в свой стандартный поток вывода. С помощью этого трюка разработчик может использовать в своей программе функциональность другой программы, не вмешиваясь во внутренние детали ее работы. Для решения этой задачи мы воспользуемся функциями popen(3) и pclose(3). Формально эти функции подобны функциям fopen(3) и fclose(3). Функция popen() запускает внешнюю программу и возвращает вызвавшему ее приложению указатель на структуру FILE, связанную либо со стандартным потоком ввода, либо со стандартным потоком вывода запущенного процесса. Первый параметр функции popen() - строка, содержащая команду, запускающую внешнюю программу. Второй параметр определяет, какой из стандартных потоков (вывода или ввода) будет возвращен. Аргумент “w” соответствует потоку ввода запускаемой программы, в этом случае приложение, вызвавшее popen(), записывает данные в поток. Аргумент “r” соответствует потоку вывода. Функция pclose() завершает работу с внешним приложением и закрывает канал.

Программа запускает шелл, и записывает данные, выводимые командой в шелле, одновременно на стандартный терминал и в файл log.txt (аналогичными функциями обладает стандартная команда tee).

 Например, если скомпилировать программу:
   gcc makelog.c -o makelog
 а затем скомандовать
   makelog "ls -al"
 на экране терминала будут распечатаны данные, выводимые командой
 оболочки ls -al, а в рабочей директории программы makelog будет создан файл
 log.txt, содержащий те же данные.
  Рассмотрим строку программы 
   f = popen(argv[1], "r");
 argv[1] - это не имя файла , а команда "ls -al"
 Если вызов popen() был успешен, мы можем считывать данные,
 выводимые запущенной командой, с помощью обычной функции fread(3):
 fread(buf, 1, BUF_SIZE, f)
  Для вывода данных, прочитанных с
 помощью fread(), на терминал мы используем функцию write() с указанием
 дескриптора стандартного потока вывода:
 write(1, buf, len);
 
 Текст программы :
 /*
   Popen/Pclose Demo by Andrei Borovsky .
   This code is freeware. 
 */
 
 #include < stdio.h>
 #include < errno.h>
 
 #define BUF_SIZE 0x100
 
 int main(int argc, char * argv[])
 {
   FILE * f;
   FILE * o;
   int len;
   char buf[BUF_SIZE];
   if (argc != 2)
   {
      printf("использование: makelog \"\"\n");
      return -1;
   }
   f = popen(argv[1], "r");
   if (f == NULL)
   {
     perror("ошибка:\n");
     return -1;
   }
   o = fopen("log.txt", "w");
   while ((len = fread(buf, 1, BUF_SIZE, f)) != 0)
   {
      write(1, buf, len);
      fwrite(buf, 1, len, o);
   }
   pclose(f);
   fclose(o);
   return 0;
 } 
 

7. Канал , создаваемый функцией pipe(2), на уровне интерфейса представляется двумя дескрипторами файлов, один из которых служит для чтения данных, а другой - для записи. Каналы не поддерживают произвольный доступ, т. е. данные могут считываться только в том же порядке, в котором они записывались. Неименованные каналы используются преимущественно вместе с функцией fork(2) и служат для обмена данными между родительским и дочерним процессами. Для организации подобного обмена данными, сначала, с помощью функции pipe(), создается канал. Функции pipe() передается единственный параметр - массив типа int, состоящий из двух элементов. В первом элементе массива функция возвращает дескриптор файла, служащий для чтения данных из канала (выход канала), а во втором - дескриптор для записи (вход). Затем, с помощью функции fork() процесс «раздваивается». Дочерний процесс наследует от родительского процесса оба дескриптора, открытых с помощью pipe(), но, также как и родительский процесс, он должен использовать только один из дескрипторов. Направление передачи данных между родительским и дочерним процессом определяется тем, какой дескриптор будет использоваться родительским процессом, а какой - дочерним. Продемонстрируем изложенное на простом примере программы pipes.c, использующей функции pipe() и fork().

 
 #include < stdio.h>
 #include < string.h>
 #include < sys/types.h>
 
 int main (int argc, char * argv[])
 {
   int pipedes[2];
   pid_t pid;
   pipe(pipedes);
   pid = fork();
   if ( pid > 0 )  
   {  
     char  *str = "String passed via pipe\n";
     close(pipedes[0]);
     write(pipedes[1], (void *) str, strlen(str) + 1); 
     close(pipedes[1]);
   }  
   else
   {
     char buf[1024];
     int len;
     close(pipedes[1]);
     while ((len = read(pipedes[0], buf, 1024)) != 0)
     write(2, buf, len); 
     close(pipedes[0]);  
   }
   return 0;
 }
 Оба дескриптора канала хранятся в переменной pipedes. После вызова fork()
 процесс раздваивается и родительский процесс (тот, в котором fork() вернула
 ненулевое значение, равное PID дочернего процесса) закрывает дескриптор,
 открытый для чтения, и записывает данные в канал, используя дескриптор,
 открытый для записи (pipedes[1]). Дочерний процесс (в котором fork() вернула 0)
 закрывает дескриптор, открытый для записи, и затем считывает данные из канала,
 используя дескриптор, открытый для чтения (pipedes[0]). Назначение
 дескрипторов легко запомнить, сопоставив их с аббревиатурой I/O (первый
 дескриптор - для чтения (input), второй - для записи (output)).
  Для передачи данных с помощью каналов используются специальные области
 памяти (созданные ядром системы), называемые буферами каналов (pipe buffers).
 Одна из важных особенностей буферов каналов заключается в том, что даже если
 предыдущая запись заполнила буфер не полностью, повторная запись данных в
 буфер становится возможной только после того, как прежде записанные данные
 будут прочитаны. Это означает, что если разные процессы, пишущие данные в
 один и тот же канал, передают данные блоками, размеры которых не превышают
 объем буферов, данные из блоков, записанных разными процессами, не будут
 перемешиваться между собой. Использование этой особенности каналов
 существенно упрощает синхронизацию передачи данных.
 

8.

 Напишем небольшую программу, которая запускает
 утилиту netstat, читает данные, выводимые этой утилитой, и распечатывает их на
 экране. Если бы мы использовали для этой цели функцию popen(), то получили бы
 доступ к потоку вывода netstat с помощью вызова
 popen("netstat", "r");
 Этот способ прост, но не эффективен. Мы напишем другую программу (файл
 printns.c). Структура этой программы та же, что и в предыдущем примере, только
 теперь родительский процесс читает данные с помощью канала явным образом.
 Самое интересное происходит в дочернем процессе, где выполняется
 последовательность функций:
 close(pipedes[0]);
 dup2(pipedes[1], 1);
 execve("/bin/netstat", NULL, NULL);
 С помощью функции dup2(2) мы перенаправляем стандартный поток вывода
 дочернего процесса (дескриптор стандартного потока вывода равен 1) в канал,
 используя дескриптор pipdes[1], открытый для записи. Далее с помощью функции
 execve(2) мы заменяем образ дочернего процесса процессом netstat (обратите
 внимание, что поскольку в нашем распоряжении нет оболочки с ее переменной
 окружения PATH, путь к исполнимому файлу netstat нужно указывать полностью).
 В результате родительский процесс может читать стандартный вывод netstat через
 поток, связанный с дескриптором pipdes[0] (и никакой оболочки!).
 
 текст программы : 
 #include < stdio.h>
 #include < string.h>
 #include < sys/types.h>
 #include < unistd.h>
 
 #define BUF_SIZE 0x100
 
 int main (int argc, char * argv[])
 {
   int pipedes[2];
   pid_t pid;
   pipe(pipedes);
   pid = fork();
   if ( pid > 0 )
   {
     char buf[BUF_SIZE];
     int len;
     close(pipedes[1]);
     while ((len = read(pipedes[0], buf, BUF_SIZE)) > 0)
       write(1, buf, len); 
     close(pipedes[0]);
   }
   else
   {
     close(pipedes[0]); 
     dup2(pipedes[1], 1);
     execve("/bin/netstat", NULL, NULL);
   } 
   return 0;
 }       
 
 

9. Для передачи данных между неродственными процессами мы воспользуемся механизмом именованных каналов (named pipes), который позволяет каждому процессу получить свой, «законный» дескриптор канала. Передача данных в этих каналах (как, впрочем, и в однонаправленных неименованных каналах) подчиняется принципу FIFO (первым записано - первым прочитано), поэтому в англоязычной литературе иногда можно встретить названия FIFO pipes или просто FIFOs. Именованные каналы отличаются от неименованных наличием имени (странно, не правда ли?), то есть идентификатора канала, потенциально видимого всем процессам системы. Для идентификации именованного канала создается файл специального типа pipe. Это еще один представитель семейства виртуальных файлов Unix, не предназначенных для хранения данных (размер файла канала всегда равен нулю). Файлы именованных каналов являются элементами VFS, как и обычные файлы Linux, и для них действуют те же правила контроля доступа. Для создания файлов именованных каналов можно воспользоваться функцией mkfifo(3). Первый параметр этой функции - строка, в которой передается имя файла канала, второй параметр - маска прав доступа к файлу. Функции mkfifo() создает канал и файл соответствующего типа. Если указанный файл канала уже существует, mkfifo() возвращает -1, (переменная errno принимает значение EEXIST). После создания файла канала, процессы, участвующие в обмене данными, должны открыть этот файл либо для записи, либо для чтения. Обратите внимание на то, что после закрытия файла канала файл (и соответствующий ему канал) продолжают существовать. Для того чтобы закрыть сам канал, нужно удалить его файл, например с помощью последовательных вызовов unlink(2).

 Рассмотрим работу именованного канала на примере простой системы клиент-
 сервер. Программа-сервер создает канал и передает в него текст, вводимый
 пользователем с клавиатуры. Программа-клиент читает текст и выводит его на
 терминал. Программы из этого примера можно рассматривать как упрощенный
 вариант системы мгновенного обмена сообщениями между пользователями
 многопользовательской ОС. Исходный текст программы-сервера хранится в файле
 typeserver.c. Вызов функции mkfifo() создает файл-идентификатор канала в
 рабочей директории программы:
 mkfifo(FIFO_NAME, 0600);
 где FIFO_NAME - макрос, задающий имя файла канала (в нашем случае -
 "./fifofile").
 В качестве маски доступа мы используем восьмеричное значение 0600,
 разрешающее процессу с аналогичными реквизитами пользователя чтение и
 запись (можно было бы использовать маску 0666, но на мы на всякий случай
 воздержимся от упоминания Числа Зверя, пусть даже восьмеричного, в нашей
 программе). Для краткости мы не проверяем значение, возвращенное mkfifo(), на
 предмет ошибок. В результате вызова mkfifo() с заданными параметрами в рабочей
 директории программы должен появиться специальный файл fifofile. Файл-
 менеджер KDE отображает файлы канала с помощью красивой пиктограммы,
 изображающей приоткрытый водопроводный кран. Далее в программе-сервере мы
 просто открываем созданный файл для записи:
 f = fopen(FIFO_NAME, "w");
 Считывание данных, вводимых пользователем, выполняется с помощью
 getchar(), а с помощью функции fputc() данные передаются в канал. Работа
 сервера завершается, когда пользователь вводит символ “q”. Исходный текст
 программы-клиента можно найти в файле typeclient.c. Клиент открывает файл
 fifofile для чтения как обычный файл:
 f = fopen(FIFO_NAME, "r");
 Символы, передаваемые по каналу, считываются с помощью функции fgetc() и
 выводятся на экран терминала с помощью putchar(). Каждый раз, когда
 пользователь сервера наживает ввод, функция fflush(), вызываемая сервером (см.
 файл typeserver.c), выполняет принудительную очистку буферов канала, в
 результате чего клиент считывает все переданные символы. Получение символа
 “q” завершает работу клиента.
 
 текст программы-клиента:
 #include < stdio.h>
 
 #define FIFO_NAME "./fifofile"
 
 int main ()
 {
   FILE * f;
   char ch;
   f = fopen(FIFO_NAME, "r");
   do
   {
     ch = fgetc(f);
     putchar(ch);
   } while (ch != 'q');
   fclose(f);
   unlink(FIFO_NAME);
   return 0;
 }
 
 текст программы-сервера :
 #include < stdio.h>
 #include < sys/types.h>
 #include < sys/stat.h>
 
 #define FIFO_NAME "./fifofile" 
 
 int main(int argc, char * argv[])
 {
   FILE * f;
   char ch;
   mkfifo(FIFO_NAME, 0600); 
   f = fopen(FIFO_NAME, "w");
   if (f == NULL) 
   { 
     printf("Не удалось открыть файл\n");
     return -1;
   }
   do
   {
     ch = getchar();
     fputc(ch, f);
     if (ch == 10) fflush(f);
   } while (ch != 'q');
   fclose(f);
   unlink(FIFO_NAME);
   return 0;
 }
 
 Запустите сначала сервер, потом клиент в разных окнах терминала. Печатайте
 текст в окне сервера. После каждого нажатия клавиши [Enter] клиент должен
 отображать строку, напечатанную на сервере.
 
 

10. Как и каналы, сокеты используют простой интерфейс, основанный на «файловых» функциях read(2) и write(2) (открывая сокет, программа Unix получает дескриптор файла, благодаря которому можно работать с сокетами, используя файловые функции), но, в отличие от каналов, сокеты позволяют передавать данные в обоих направлениях, как в синхронном, так и в асинхронном режиме.

 Сокеты можно разделить на несколько типов :
 1. Сокеты в файловом пространстве имен - 
   используют в качестве адресов имена файлов специального типа.
   Важной особенностью этих сокетов является то, что соединение с их помощью
   локального и удаленного приложений невозможно
   sock = socket(AF_UNIX, SOCK_DGRAM, 0);
 
 2. Парные сокеты - socket pairs - работают аналогично неименованным каналам
   if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
 
 3. Сетевые сокеты .
 
 

11.

 В качестве примера мы рассмотрим комплекс из двух приложений, клиента и
 сервера, использующих сетевые сокеты для обмена данными. Текст программы
 сервера вы найдете в файле netserver.c, ниже мы приводим некоторые фрагменты.
 Прежде всего, мы должны получить дескриптор сокета:
 sock = socket(AF_INET, SOCK_STREAM, 0);
 if (socket < 0) {
 printf("socket() failed: %d\n", errno);
 return EXIT_FAILURE;
 }
 В первом параметре функции socket() мы передаем константу AF_INET,
 указывающую на то, что открываемый сокет должен быть сетевым. Значение
 второго параметра требует, чтобы сокет был потоковым. Далее мы, как и в случае
 сокета в файловом пространстве имен, вызываем функцию bind():
 serv_addr.sin_family = AF_INET;
 serv_addr.sin_addr.s_addr = INADDR_ANY;
 serv_addr.sin_port = htons(port);
 if (bind(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
 printf("bind() failed: %d\n", errno);
 return EXIT_FAILURE;
 }
 
 Переменная serv_addr, - это структура типа sockaddr_in. Тип sockaddr_in
 специально предназначен для хранения адресов в формате Интернета. Самое
 главное отличие sockaddr_in от sockaddr_un – наличие параметра sin_port,
 предназначенного для хранения значения порта. Функция htons() переписывает
 двухбайтовое значение порта так, чтобы порядок байтов соответствовал
 принятому в Интернете (см. врезку). В качестве семейства адресов мы указываем
 AF_INET (семейство адресов Интернета), а в качестве самого адреса –
 специальную константу INADDR_ANY. Благодаря этой константе наша программа
 сервер зарегистрируется на всех адресах той машины, на которой она
 выполняется.
 
 Мы вызываем функцию listen(2), которая переводит сервер в режим ожидания запроса на
 соединение:
 listen(sock, 1);
 Второй параметр listen() – максимальное число соединений, которые сервер
 может обрабатывать одновременно. Далее мы вызываем функцию accept(2),
 которая устанавливает соединение в ответ на запрос клиента:
 newsock = accept(sock, (struct sockaddr *) &cli_addr, &clen);
 if (newsock < 0) {
 printf("accept() failed: %d\n", errno);
 return EXIT_FAILURE;
 }
 
 Получив запрос на соединение, функция accept() возвращает новый сокет,
 открытый для обмена данными с клиентом, запросившим соединение. Сервер как
 бы перенаправляет запрошенное соединение на другой сокет, оставляя сокет sock
 свободным для прослушивания запросов на установку соединения. Второй
 параметр функции accept() содержит сведения об адресе клиента, запросившего
 соединение, а третий параметр указывает размер второго. Так же как и при
 вызове функции recvfom(), мы можем передать NULL в последнем и
 предпоследнем параметрах. Для чтения и записи данных сервер использует
 функции read() и write(), а для закрытия сокетов, естественно, close().
 В программе-клиенте (netclient.c) нам, прежде всего, нужно решить задачу, с
 которой мы не сталкивались при написании сервера, а именно выполнить
 преобразование доменного имени сервера в его сетевой адрес. Разрешение
 доменных имен выполняет функция gethostbyname():
 server = gethostbyname(argv[1]);
 if (server == NULL) {
 printf("Host not found\n");
 return EXIT_FAILURE;
 }
 Функция получает указатель на строку с Интернет-именем сервера (например,
 www.unix.com или 192.168.1.16) и возвращает указатель на структуру hostent
 (переменная server), которая содержит имя сервера в приемлемом для
 дальнейшего использования виде. При этом, если необходимо, выполняется
 разрешение доменного имени в сетевой адрес. Далее мы заполняем поля
 переменной serv_addr (структуры sockaddr_in) значениями адреса и порта:
 serv_addr.sin_family = AF_INET;
 memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
 serv_addr.sin_port = htons(port);
 Программа-клиент открывает новый сокет с помощью вызова функции socket()
 аналогично тому, как это делает сервер (дескриптор сокета, который возвращает
 socket() мы сохраним в переменной sock), и вызывает функцию connect(2) для
 установки соединения:
 if (connect(sock, &serv_addr, sizeof(serv_addr)) < 0) {
 printf("connect() failed: %d", errno);
 return EXIT_FAILURE;
 }
 
 Теперь сокет готов к передаче и приему данных. Программа-клиент считывает
 символы, вводимые пользователем в окне терминала. Когда пользователь
 нажимает <Ввод> программа передает данные серверу, ждет ответного
 сообщения сервера и распечатывает его.
 
 

12. core.dump

 
 

13. их можно разделить на 2 больших группы :

 1. классические сигналы - их 31
 2. сигналы реального времени
 
 примеры классических сигналов :
 SIGHUP (номер 1) - изначально был предназначен для того, чтобы
 информировать программу о потере связи с управляющим терминалом
 
 SIGINT (номер 2) - обычно посылается процессу, если пользователь
 терминала дал команду прервать процесс (обычно эта команда – сочетание
 клавиш Ctrl-C) .
 
 SIGABRT (номер 6) - посылается программе в результате вызова функции
 abort(3). В результате программа завершается с сохранением на диске образа
 памяти.
 
 SIGKILL (номер 9) - завершает работу программы. Программа не может
 ни обработать, ни игнорировать этот сигнал.
 
 SIGSEGV (номер 11) - посылается процессу, который пытается
 обратиться к не принадлежащей ему области памяти. Если обработчик сигнала не
 установлен, программа завершается с сохранением на диске образа памяти.
 
 SIGTERM (номер 15) - вызывает «вежливое» завершение программы.
 
 SIGCHLD (номер 17) - посылается процессу в том случае, если его
 дочерний процесс завершился или был приостановлен. Родительский процесс
 
 SIGCONT (номер 18) - возобновляет выполнение процесса,
 остановленного сигналом SIGSTOP.
 
 SIGSTOP (номер 19) - приостанавливает выполнение процесса.
 
 SIGTSTP (номер 20) - приостанавливает процесс по команде
 пользователя (обычно эта команда – сочетание клавиш Ctrl-Z).
 
  SIGIO/SIGPOLL (номер29)  - сообщает процессу, что на одном из дескрипторов, открытых асинхронно,
 появились данные. По умолчанию этот сигнал, как ни странно, завершает работу
 программы.
 
 
 
   
 

14.

 #include < stdio.h>
 #include < stdlib.h>
 #include < signal.h>
 void term_handler(int i)
 { printf ("Terminating\n");
 exit(EXIT_SUCCESS);
 }
 int main(int argc, char ** argv) {
 struct sigaction sa;
 sigset_t newset;
 sigemptyset(&newset);
 sigaddset(&newset, SIGHUP);
 sigprocmask(SIG_BLOCK, &newset, 0);
 sa.sa_handler = term_handler;
 sigaction(SIGTERM, &sa, 0);
 printf("My pid is %i\n", getpid());
 printf("Waiting...\n");
 while(1) sleep(1);
 return EXIT_FAILURE;
 }
 Наша программа делает две вещи: обрабатывает сигнал SIGTERM (при
 получении этого сигнала программа выводит диагностическое сообщение и
 завершает свою работу) и блокирует сигнал SIGHUP, так что этот сигнал не может
 завершить ее работу. В тексте программы мы первым делом определяем функцию-
 обработчик сигнала SIGTERM term_handler(). Функции-обработчики сигналов – это
 обычные функции Си, они имеют доступ ко всем глобально видимым переменным
 и функциям. Однако, поскольку мы не знаем, в какой момент выполнения
 программы будет вызвана функция-обработчик, мы должны проявлять особую
 осторожность при обращении к глобальным структурам данных из этой функции.
 Для функций, обрабатывающих потоки, существует и еще одно важное требование
 – реентерабильность. Поскольку обработчик сигнала может быть вызван в любой
 точке выполнения программы (а при не кототорых условиях во время обработки
 одного сигнала может быть вызван другой обработчик сигнала) в обработчиках
 додлжны использоваться функции, которые удовлетворяют требованию
 реентерабельности, то есть, могут быть вызваны в то время, когда они уже
 вызваны где-то в другой точке программы. Фактически, требование
 реентерабельности сводится к тому, чтобы функция не использовала никаких
 глобальных ресурсов, не позаботившись о синхронизации доступа к этим
 ресурсам. Некоторые функции ввода-вывода, в том числе, функция printf(),
 которую мы (и не только мы) используем в примерах обработчиков сигналов,
 реентерабельными не являются. Это значит, что выводу одной функции printf()
 может помешать вывод другой функции. В приложении приводится список
 реентерабельных функций, которые безопасно вызвать из обработчиков сигналов.
 Единственным параметром нашего варианта функции-обработчика сигнала (в
 Unix-системах существует и другой вариант) является переменная типа int, в
 которой передается номер сигнала, вызвавшего обработчик. Нам этот номер не
 нужен, поскольку мы знаем, что только один сигнал, - SIGTERM, может вызвать
 нашу функцию, однако, в принципе, ничто не мешает нам использовать одну
 функцию для обработки нескольких разных сигналов, и тогда параметр функции-
 обработчика будет иметь для нас смысл. Функция-обработчик не возвращает
 никакого значения, что вполне логично, так как она вызывается не нашей
 программой, а неким системным компонентом. Особый интерес представляет
 завершение программы из обработчика сигнала. Назначение обработчика сигналу
 SIGTERM означает, что умалчиваемое действие сигнала, – завершение
 программы, не будет выполняться автоматически, и нам необходимо (если,
 конечно, мы хотим, чтобы этот сигнал завершал программу) позаботиться об этом
 явным образом. Если вы закомментируете вызов exit() в нашем примере, то
 увидите, что программа не будет завершать по получении сигнала SIGTERM. В
 принципе, вы можете придать сигналу SIGTERM совершенно иной смысл,
 например, оповещать программу о наступлении времени вашей любимой
 телепередачи (или о выходе нового номера журнала Linux Format), однако
 назначать стандартным сигналам нестандартные действия категорически не
 рекомендуется. Обработчик SIGTERM предназначен для того, чтобы, по
 требованию системы или пользователя, программа могла быстро и элегантно
 закончить текущую задачу и завершить свое выполнение. Именно этим
 обработчик и должен заниматься.
 Перейдем теперь к тексту главной функции программы. Установка и удаление
 обработчиков сигналов осуществляются функцией sigaction(2). Первым
 параметром этой функции является номер сигнала, а в качестве второго и
 третьего параметров следует передать указатели на структуру sigaction. Эта
 структура содержит данные об операции, выполняемой над обработчиком сигнала.
 Второй параметр sigaction() служит для передачи новых значений для обработки
 сигнала, а третий – возвращает ранее установленные значения. В таблице 1
 приводится краткое описание полей структуры sigaction.
 Таблица 1. Поля структуры sigaction.
 Поле        Значение
 sa_handler  Указатель на функцию обработчик сигнала или константа.
 sa_mask     Маска сигналов, блокируемых на время вызова обработчика.
 sa_flags    Дополнительные флаги.
 Поле sa_handler должно содержать либо адрес функции-обработчика, либо
 специальную константу, указывающую, что нужно делать с сигналом. Константа
 SIG_IGN указывает, что сигнал следует игнорировать, а константа SIG_DFL – что
 нужно восстановить обработку сигнала, заданную системой по умолчанию. Поле
 sa_mask позволяет заблокировать некоторое множество сигналов на время
 выполнения обработчика данного сигнала. Делается это для того, чтобы
 обработка других сигналов не могла прервать обработку данного (это может быть
 необходимо, особенно, если один обработчик обрабатывает несколько разных
 сигналов). Параметр sa_flags позволяет задать ряд флагов для выполнения более
 тонкой настройки обработчика сигналов. Например, флаг SA_RESETHAND
 указывает, что после завершения обработки сигнала заданным обработчиком
 должен быть восстановлен обработчик, заданный по умолчанию, так что все
 последующие сигналы будут обрабатываться умалчиваемым обработчиком.
 В результате вызова функции sigaction() мы устанавливаем обработчик сигнала
 SIGTERM. Затем наша программа распечатывает значение PID (это значение
 понадобится нам для вызова команды kill) и входит в бесконечный цикл, из
 которого она может быть выведена одним из сигналов. Следует отметить, что
 функция sleep() возвращает управление (возобновляет выполнение программы
 раньше срока) если обработчик какого-либо сигнала возвращает управление в этот
 момент. Иначе говоря, любой обрабатываемый сигнал прерывает выполнение
 sleep(). Впрочем, в нашем примере с бесконечным циклом это не помогло бы
 программе завершиться. Сигнал SIGTERM приведет к тому, что программа выдаст
 диагностическое сообщение и завершит работу, а сигналы SIGINT и SIGABRT – к
 тому, что программа завершится без всякого сообщения. Скомпилируйте и
 запустите программу в окне терминала. В другом окне скомандуйте
 kill 
 где PID – идентификатор процесса программы. Вы увидите, что перед тем как
 завершиться программа выдает диагностическое сообщение, тогда как при
 завершении с помощью Ctrl-C никакого сообщения не выводится.
 
 
 

15. Процесс - это выполняющийся экземпляр программы. Для каждого процесса Linux ядро системы поддерживает специальную структуру данных task_struct, в которой хранятся важнейшие параметры процесса (процессы уровня пользователя не имеют доступа к этой структуре). В структуре task_struct есть специальное поле, в котором хранится численный идентификатор процесса (Process Identifier, PID). Процесс создается с помощью fork , clone , sys_clone .

 
 

16.

 Функция fork() возвращает управления дважды, двум копиям исходного процесса (родительской и дочерней).
 Различия родительского и дочернего процессов :
 · Родительскому процессу функция fork() возвращает PID дочернего процесса, а
 дочернему процессу – значение 0. Именно по возвращенному fork() значению
 процесс может узнать, дочерний он или родительский. В иерархии процессов
 родительский процесс оказывается родителем (странно, не правда ли)
 дочернего процесса. Дочерний процесс может получить PID родительского
 процесса с помощью вызова getppid(2), в то время как родительский процесс
 может узнать PID своего дочернего процесса только из результата fork().
 Именно поэтому fork() может позволить себе не возвращать значение PID
 дочернему процессу, но обязана возвращать значение PID дочернего процесса
 родительскому.
 · Значения tms_utime, tms_stime, tms_cutime, и tms_cstime в дочернем процессе
 обнуляются.
 · Блокировки на уровне файлов (file locks), установленные в родительском
 процессе, в дочернем процессе снимаются (иначе сразу два процесса оказались
 бы владельцами блокировок).
 · Сигналы, обработка которых была отложена в родительском процессе в момент
 вызова fork(), в дочернем процессе сбрасываются.
 
 

17.

 Если вы хотите, чтобы новая программа работала одновременно со старой, нужно сначала
 раздвоить процесс с помощью fork(), а затем заместить образ программы в одной
 из копий образом новой программы. Для замены образа одной программы образом
 другой применяются функции семейства exec*. Функций этого семейства всего
 шесть: execl(), execlp(), execle(), execv(), execvp() и execv(). Если вы присмотритесь
 к названиям функций, то увидите, что первые три имеют имена вида execl*, а
 вторые три – имена вида execv*. Эти функции отличаются списками параметров, а
 также тем, как обрабатывается имя файла.
 Рассмотрим, например, функции execve(2) и execvp(2). Заголовок функции
 execve(2) имеет вид:
 int execve(const char *pathname, char *const argv[], char *const envp []);
 Первый параметр функции execve(), - это имя исполнимого файла запускаемой
 программы (исполнимым файлом может быть двоичный файл или файл сценария
 оболочки, в котором в первой строке указан интерпретатор). Имя исполнимого
 файла для этой функции должно включать полный путь к файлу, (путь, начиная с
 корневого слеша, точки или тильды). Второй параметр функции, представляет
 собой список аргументов командной строки, которые должны быть переданы
 запускаемой программе. Формат этого списка должен быть таким же, как у списка
 argv[], который получает функция main(), то есть, первым элементом должно быть
 имя запущенной программы, а последним – нулевой указатель на строку, то есть
 (char *) NULL
 В последнем параметре функции execve() передается список переменных среды
 окружения формате
 ИМЯ=ЗНАЧЕНИЕ
 Массив переменных должен заканчиваться нулевым указателем на строку, как
 и argv. Копию набора переменных окружения, полученного вашей программой от
 оболочки, можно получить из внешней переменной environ, которую вы должны
 объявить в файле вашей программы:
 extern char ** environ;
 Передача списка переменных окружения явным образом позволяет вам, в
 случае необходимости, модифицировать список переменных, заданных по
 умолчанию. В параметре argv, как и в параметре envp, можно передавать
 значения NULL, если вы уверены, что вызываемой программе не нужны
 переменные окружения или командная строка. Заголовок функции execvp()
 выглядит проще:
 int execvp(const char *file, char *const argv[]);
 Первый параметр функции, это имя запускаемого файла. Функция execvp()
 выполняет поиск имен с учетом значения переменной окружения PATH, так что
 для программы, которая расположена в одной из директорий, перечисленных в
 PATH, достаточно указывать только имя исполнимого файла. Второй параметр
 функции execvp() соответствует второму параметру execve(). У функции execvp()
 нет параметра для передачи набора переменных среды окружения, но это не
 значит, что запущенная программа не получит эти переменные. Программе будет
 передана копия набора переменных окружения родительского процесса.
 
 

18.

 Вы можете изменить переменные среды окружения не только путем
 модификации списка environ, но и с помощью набора специальных функций. Для
 установки новой переменной окружения используется putenv(3). У функции
 putenv() один параметр типа char *. В этом параметре функции передается строка
 ИМЯ=ЗНАЧЕНИЕ, устанавливающая переменную окружения. Функция
 возвращает 0 в случае успеха и -1 в случае ошибки. Программа может прочитать
 значение переменной окружения с помощью вызова getenv(3). У этой функции так
 же один параметр типа «строка с нулевым конечным символом». В этом
 параметре передается имя переменной, значение которой мы хотим прочитать. В
 случае успеха функция возвращает строку, содержащую значение переменной, а в
 случае неудачи (например, если запрошенной переменной не существует) – NULL.
 Переменные среды окружения играют важную роль в работе процессов,
 поскольку многие системные функции используют их для получения различных
 параметров, необходимых для нормальной работы программ, однако, управление
 программами с помощью переменных окружения считается морально устаревшим
 методом. Если ваша программа должна получать какие-то данные извне,
 воздержитесь от создания новых переменных окружения. Современные
 программы чаще полагаются на файлы конфигурации, средства IPC и прикладные
 интерфейсы. Старайтесь так же не использовать переменные окружения для
 получения тех данных, которые вы можете получить с помощью специальных
 функций C, например, используйте getcwd(3) вместо обращения к переменной
 PWD. Полный список установленных переменных окружения и их значений вы
 можете получить с помощью команды env, а некоторые наиболее важные
 переменные приведены в таблице:
 
 Переменная  Описание
 DISPLAY    Имя дисплея X Window
 HOME       Полный путь к домашней директории пользователя-владельца процесса
 HOST       Имя локального узла
 LANG       Текущая локаль
 LOGNAME    Имя пользователя-владельца процесса
 PATH       Список директорий для поиска имен файлов
 PWD        Полный путь к текущей рабочей директории
 SHELL      Имя оболочки, заданной для пользователя-владельцапроцесса по умолчанию
 TERM       Терминал пользователя-владельца процесса по умолчанию
 TMPDIR     Полный путь к директории для хранения временных файлов
 TZ         Текущая временная зона
 
 Помимо функций getenv() и putenv() есть еще несколько дополнительных
 функций для работы с переменными среды окружения. Функция setenv() может
 быть использована для созданяи новой переменной. В отличие от функции
 putenv(), эта функция позволяет установить флаг, благодаря которому значение
 уже существующей переменной не будет изменено. С помощью функции
 unsetenv() вы можете удалить переменную окружения. Наконец, если переменные
 среды окружения вам надоели, вы можете воспользоваться функцией clearenv()
 для удаления всего списка переменных. Под удалением переменных окружения
 подразумевается их удаление из среды окружения текущего процесса и его
 потомков. Это удаление никак не повлияет на переменные среды окружения
 других процессов.
 
 

19.

 Рассмотрим использование функции execvp() на примере программы,
 запускающей другую программу, имя которой передается ей в качестве аргумента
 командной строки. Назовем нашу программу exec (ее исходные тексты вы найдете
 на прилагаемом диске в файле exec.c). Если вы скомпилируете файл exec.c и
 скомандуете, например
   ./exec ls -al
 программа выполнит команду ls –al и возвратит сведения о том, как был
 завершен соответствующий процесс. Ниже приводится полный исходный текст
 exec.
 #include < sys/types.h>
 #include < sys/wait.h>
 #include < stdlib.h>
 #include < stdio.h>
 #include < errno.h>
 int main(int argc, char * argv[])
 { int pid, status;
 if (argc < 2) {
 printf("Usage: %s command, [arg1 [arg2]...]\n", argv[0]);
 return EXIT_FAILURE;
 }
 printf("Starting %s...\n", argv[1]);
 pid = fork();
 if (pid == 0) {
 execvp(argv[1], &argv[1]);
 perror("execvp");
 return EXIT_FAILURE; // Never get there normally
 } else {
 if (wait(&status) == -1) {
 perror("wait");
 return EXIT_FAILURE;
 }
 if (WIFEXITED(status))
 printf("Child terminated normally with exit code %i\n",
 WEXITSTATUS(status));
 if (WIFSIGNALED(status))
 printf("Child was terminated by a signal #%i\n", WTERMSIG(status));
 if (WCOREDUMP(status))
 printf("Child dumped core\n");
 if (WIFSTOPPED(status))
 printf("Child was stopped by a signal #%i\n", WSTOPSIG(status));
 }
 return EXIT_SUCCESS;
 }
 Поскольку мы хотим, чтобы новая программа не заменяла старую, а
 выполнялась одновременно с ней, мы раздваиваем процесс с помощью fork(). В
 дочернем процессе, которому fork() возвращает 0, мы выполняем вызов execvp().
 Первый параметр execvp() – значение argv[1], в котором должно быть передано
 имя запускаемой программы. В качестве второго параметра функции передается
 массив аргументов командной строки, полученных программой exec, начиная со
 второго элемента (элемент argv[1]). Например, если список аргументов
 программы exec имел вид "exec", "ls", "-al", то первым параметром функции
 execvp() будет строка "ls", а вторым параметром – массив из двух строк "ls" и "-al"
 (не забывайте, что первым элементом массива аргументов командной строки
 должно быть имя самой программы, по которому она была вызвана). Таким
 образом, вы можете, например, давать команды
 ./exec ls –al
 ./exec ./exec ls –al
 ./exec ./exec ./exec ls –al
 и так далее. Чего вы не можете, однако, сделать, так это выполнить с помощью
 нашей программы команду типа
 ./exec "ls > log.txt"
 
 

20.

 Первая подсистема потоков в Linux появилась в 1996 году и называлась, без
 лишних затей, – LinuxThreads. Рудимент этой подсистемы, который вы найдете в
 любой современной системе Linux, – файл /usr/include/pthread.h, указывает год
 релиза – 1996 и имя разработчика – Ксавье Лерой (Xavier Leroy). Библиотека
 LinuxThreads была попыткой организовать поддержку потоков в Linux в то время,
 когда ядро системы еще не предоставляло никаких специальных механизмов для
 работы с потоками. Позднее разработку потоков для Linux вели сразу две
 конкурирующие группы – NGPT и NPTL. В 2002 году группа NGPT фактически
 присоединилась к NPTL и теперь реализация потоков NPTL является стандартом
 Linux. Подсистема потоков Linux стремится соответствовать требованиям
 стандартов POSIX, так что новые многопоточные приложения Linux должны без
 проблем компилироваться в POSIX-совместимых системах.
 
 

21.

 В среде Microsoft Windows процесс представляет собой контейнер для потоков
 Процесс-контейнер содержит, как минимум, один поток. Если потоков в процессе
 несколько, приложение (процесс) становится многопоточным. В мире Linux все
 выглядит иначе. В Linux каждый поток является процессом и для того чтобы
 создать новый поток, нужно создать новый процесс. В чем же, в таком случае,
 заключается преимущество многопоточности Linux перед многопроцессностью? В
 многопоточных приложениях Linux для создания дополнительных потоков
 используются процессы особого типа. Эти процессы представляют собой обычные
 дочерние процессы главного процесса, но они разделяют с главным процессом
 адресное пространство, файловые дескрипторы и обработчики сигналов. Для
 обозначения процессов этого типа, применяется специальный термин – легкие
 процессы (lightweight processes). Прилагательное «легкий» в названии процессов-
 потоков вполне оправдано. Поскольку этим процессам не нужно создавать
 собственную копию адресного пространства (и других ресурсов) своего процесса-
 родителя, создание нового легкого процесса требует значительно меньших затрат,
 чем создание полновесного дочернего процесса. Поскольку потоки Linux на самом
 деле представляют собой процессы, в мире Linux нельзя говорить, что один
 процесс содержит несколько потоков. 
 
 

22.

 В Linux у каждого процесса есть идентификатор. 
 Есть он, естественно, и у процессов-потоков. С другой стороны,
 спецификация POSIX 1003.1c требует, чтобы все потоки многопоточного
 приложения имели один идентификатор. Вызвано это требование тем, что для
 многих функций системы многопоточное приложение должно представляться как
 один процесс с одним идентификатором. Проблема единого идентификатора
 решается в Linux весьма элегантно. Процессы многопоточного приложения
 группируются в группы потоков (thread groups). Группе присваивается
 идентификатор, соответствующий идентификатору первого процесса
 многопоточного приложения. Именно этот идентификатор группы потоков
 используется при «общении» с многопоточным приложением. Функция getpid(2),
 возвращает значение идентификатора группы потока, независимо от того, из
 какого потока она вызвана. Функции kill(), waitpid() и им подобные по умолчанию
 также используют идентификаторы групп потоков, а не отдельных процессов. Вам
 вряд ли понадобится узнавать собственный идентификатор процесса-потока, но
 если вы захотите это сделать, придется воспользоваться довольно экзотичной
 конструкцией. Получить идентификатор потока (thread ID) можно с помощью
 функции gettid(2), однако саму функцию нужно еще определить с помощью
 макроса _syscall. Работа с функцией gettid() выглядит примерно так:
 #include < sys/types.h>
 #include < linux/unistd.h>
 ...
 _syscall0(pid_t,gettid);
 ...
 pid_t my_tid;
 my_tid = gettid();
 
 

23.

 Потоки создаются функцией pthread_create(3), объявленной в заголовочном
 файле < pthread.h>. Первый параметр этой функции представляет собой указатель
 на переменную типа pthread_t, которая служит идентификатором создаваемого
 потока. Второй параметр, указатель на переменную типа pthread_attr_t,
 используется для передачи атрибутов потока. Третьим параметром функции
 pthread_create() должен быть адрес функции потока. Эта функция играет для
 потока ту же роль, что функция main() – для главной программы. Четвертый
 параметр функции pthread_create() имеет тип void *. Этот параметр может
 использоваться для передачи значения, возвращаемого функцией потока. Вскоре
 после вызова pthread_create() функция потока будет запущена на выполнение
 параллельно с другими потоками программы. Таким образом, собственно, и
 создается новый поток. Я говорю, что новый поток запускается «вскоре» после
 вызова pthread_create() потому, что перед тем как запустить новую функцию
 потока, нужно выполнить некоторые подготовительные действия, а поток-родитель
 между тем продолжает выполняться. Непонимание этого факта может привести
 вас к ошибкам, которые трудно будет обнаружить. Если в ходе создания потока
 возникла ошибка, функция pthread_create() возвращает ненулевое значение,
 соответствующее номеру ошибки.
 
 

24.

 Ниже приводится фрагмент листинга программы threads
 
 #include < stdlib.h>
 #include < stdio.h>
 #include < errno.h>
 #include < pthread.h>
 void * thread_func(void *arg)
 { int i;
 int loc_id = * (int *) arg;
 for (i = 0; i < 4; i++) {
 printf("Thread %i is running\n", loc_id);
 sleep(1);
 }
 }
 int main(int argc, char * argv[])
 { int id1, id2, result;
 pthread_t thread1, thread2;
 id1 = 1;
 result = pthread_create(&thread1, NULL, thread_func, &id1);
 if (result != 0) {
 perror("Creating the first thread");
 return EXIT_FAILURE;
 }
 id2 = 2;
 result = pthread_create(&thread2, NULL, thread_func, &id2);
 if (result != 0) {
 perror("Creating the second thread");
 return EXIT_FAILURE;
 }
 result = pthread_join(thread1, NULL);
 if (result != 0) {
 perror("Joining the first thread");
 return EXIT_FAILURE;
 }
 result = pthread_join(thread2, NULL);
 if (result != 0) {
 perror("Joining the second thread");
 return EXIT_FAILURE;
 }
 printf("Done\n");
 return EXIT_SUCCESS;
 }
 Рассмотрим сначала функцию thread_func(). Это и
 есть функция потока. Наша функция потока очень проста. В качестве аргумента
 ей передается указатель на переменную типа int, в которой содержится номер
 потока. Функция потока распечатывает этот номер несколько раз с интервалом в
 одну секунду и завершает свою работу. В функции main() вы видите две
 переменных типа pthread_t. Мы собираемся создать два потока и у каждого из них
 должен быть свой идентификатор. Вы также видите две переменные типа int, id1
 и id2, которые используются для передачи функциям потоков их номеров. Сами
 потоки создаются с помощью функции pthread_create().В этом примере мы не
 модифицируем атрибуты потоков, поэтому во втором параметре в обоих случаях
 передаем NULL. Вызывая pthread_create() дважды, мы оба раза передаем в
 качестве третьего параметра адрес функции thread_func, в результате чего два
 созданных потока будут выполнять одну и ту же функцию. Функция, вызываемая
 из нескольких потоков одновременно, должна обладать свойством
 реентерабельности (этим же свойством должны обладать функции, допускающие
 рекурсию). Реентерабельная функция, это функция, которая может быть вызвана
 повторно, в то время, когда она уже вызвана (отсюда и происходит ее название).
 Реентерабельные функции используют локальные переменные (и локально
 выделенную память) в тех случаях, когда их не-реентерабельные аналоги могут
 воспользоваться глобальными переменными.
 Мы вызываем последовательно две функции pthread_join() для того, чтобы
 дождаться завершения обоих потоков. Если мы хотим дождаться завершения всех
 потоков, порядок вызова функций pthread_join() для разных потоков, очевидно, не
 имеет значения.
 Для того, чтобы скомпилировать программу threads.c, необходимо дать
 команду:
 gcc threads.c -D_REENTERANT -I/usr/include/nptl -L/usr/lib/nptl –lpthread
 -o threads
 Команда компиляции включает макрос _REENTERANT. Этот макрос указывает,
 что вместо обычных функций стандартной библиотеки к программе должны быть
 подключены их реентерабельные аналоги. Реентерабельный вариант библиотеки
 glibc написан таким образом, что вы, скорее всего, вообще не обнаружите никаких
 различий в работе с реентерабельными функциями по сравнению с их обычными
 аналогами. Мы указываем компилятору путь для поиска заголовочных файлов и
 путь для поиска библиотек /usr/include/nptl и /usr/lib/nptl соответственно. Наконец,
 мы указываем компоновщику, что программа должна быть связана с библиотекой
 libpthread, которая содержит все специальные функции, необходимые для работы
 с потоками.
 
 

25.

 Функции потоков можно рассматривать как вспомогательные программы,
 находящиеся под управлением функции main(). Точно так же, как при управлении
 процессами, иногда возникает необходимость досрочно завершить процесс,
 многопоточной программе может понадобиться досрочно завершить один из
 потоков. Для досрочного завершения потока можно воспользоваться функцией
 pthread_cancel(3). Единственным аргументом этой функции является
 идентификатор потока. Функция pthread_cancel() возвращает 0 в случае успеха и
 ненулевое значение в случае ошибки. Несмотря на то, что pthread_cancel() может
 завершить поток досрочно, ее нельзя назвать средством принудительного
 завершения потоков. Дело в том, что поток может не только самостоятельно
 выбрать порядок завершения в ответ на вызов pthread_cancel(), но и вовсе
 игнорировать этот вызов. Вызов функции pthread_cancel() следует рассматривать
 как запрос на выполнение досрочного завершения потока. Функция
 pthread_setcancelstate(3) определяет, будет ли вызвавший ее поток реагировать на
 обращение к нему с помощью pthread_cancel(), или не будет. У функции
 pthread_setcancelstate() два параметра, параметр state типа int и параметр
 oldstate типа «указатель на int». В первом параметре передается новое значение,
 указывающее, как поток должен реагировать на запрос pthread_cancel(), а в
 переменную, чей адрес был передан во втором параметре, функция записывает
 прежнее значение. Если прежнее значение вас не интересует, во втором
 параметре можно передать NULL. Чаще всего функция pthread_setcancelstate()
 используется для временного запрета завершения потока. Допустим, мы
 программируем поток, и знаем, что при определенных условиях программа может
 потребовать его досрочного завершения. Но в нашем потоке есть участок кода, во
 время выполнения которого завершать поток крайне нежелательно. Мы можем
 оградить этот участок кода от досрочного завершения с помощью пары вызовов
 pthread_setcancelstate():
 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
 ... //Здесь поток завершать нельзя
 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
 Первый вызов pthread_setcancelstate() запрещает досрочное завершение
 потока, второй – разрешает. Если запрос на досрочное завершение потока
 поступит в тот момент, когда поток игнорирует эти запросы, выполнение запроса
 будет отложено до тех пор, пока функция pthread_setcancelstate() не будет вызвана
 с аргументом PTHREAD_CANCEL_ENABLE.
 

26. Рассмотрим программу

 #include < stdlib.h>
 #include < stdio.h>
 #include < errno.h>
 #include < pthread.h>
 #include < semaphore.h>
 sem_t sem;
 void * thread_func(void *arg)
 { int i;
 int loc_id = * (int *) arg;
 sem_post(&sem);
 for (i = 0; i < 4; i++) {
 printf("Thread %i is running\n", loc_id);
 sleep(1);
 }
 }
 int main(int argc, char * argv[])
 { int id, result;
 pthread_t thread1, thread2;
 id = 1;
 sem_init(&sem, 0, 0);
 result = pthread_create(&thread1, NULL, thread_func, &id);
 if (result != 0) {
 perror("Creating the first thread");
 return EXIT_FAILURE;
 }
 sem_wait(&sem);
 id = 2;
 result = pthread_create(&thread2, NULL, thread_func, &id);
 if (result != 0) {
 perror("Creating the second thread");
 return EXIT_FAILURE;
 }
 result = pthread_join(thread1, NULL);
 if (result != 0) {
 perror("Joining the first thread");
 return EXIT_FAILURE;
 }
 result = pthread_join(thread2, NULL);
 if (result != 0) {
 perror("Joining the second thread");
 return EXIT_FAILURE;
 }
 sem_destroy(&sem);
 printf("Done\n");
 return EXIT_SUCCESS;
 }
 В новом варианте программы мы используем одну переменную id для передачи
 значения обоим потокам. Если вы скомпилируете и запустите программу threads2,
 то увидите, что она работает корректно. Секрет нашего успеха заключается в
 использовании средств синхронизации. Для синхронизации потоков мы
 задействовали семафоры. Читатели этой серии статей уже знакомы с семафорами
 System V, предназначенными для синхронизации процессов. В данном случае мы
 применяем семафоры другого типа – семафоры POSIX, которые специально
 предназначены для работы с потоками. Все объявления функций и типов,
 относящиеся к этим семафорам, можно найти в файле
 /usr/include/nptl/semaphore.h. Семафоры POSIX создаются (инициализируются) с
 помощью функции sem_init(3). Первый параметр функции sem_init() – указатель на
 переменную типа sem_t, которая служит идентификатором семафора. Второй
 параметр - pshared - в настоящее время не используется, и мы оставим его
 равным нулю. В третьем параметре функции sem_init() передается значение,
 которым инициализируется семафор. Дальнейшая работа с семафором
 осуществляется с помощью функций sem_wait(3) и sem_post(3). Единственным
 аргументом функции sem_wait() служит указатель на идентификатор семафора.
 Функция sem_wait() приостанавливает выполнение вызвавшего ее потока до тех
 пор, пока значение семафора не станет большим нуля, после чего функция
 уменьшает значение семафора на единицу и возвращает управление. Функция
 sem_post() увеличивает значение семафора, идентификатор которого был передан
 ей в качестве параметра, на единицу. Присвоив семафору значение 0, наша
 программа создает первый поток и вызывает функцию sem_wait(). Эта функция
 приостановит выполнение функции main() до тех пор, пока функция потока не
 вызовет функцию sem_post(), а это случится только после того как функция потока
 обработает значение переменной id. Таким образом, мы можем быть уверены, что
 в момент создания второго потока первый поток уже закончит работу с
 переменной id, и мы сможем использовать эту переменную для передачи данных
 второму потоку. После завершения обоих потоков мы вызываем функцию
 sem_destroy(3) для удаления семафора и высвобождения его ресурсов.
 
 

27.

 Семафоры – не единственное средство синхронизации потоков. Для
 разграничения доступа к глобальным объектам потоки могут использовать
 мьютексы. Все функции и типы данных, имеющие отношение к мьютексам,
 определены в файле pthread.h. Мьютекс создается вызовом функции
 pthread_mutex_init(3). В качестве первого аргумента этой функции передается
 указатель на переменную pthread_mutex_t, которая играет роль идентификатора
 нового мьютекса. Вторым аргументом функции pthread_mutex_init() должен быть
 указатель на переменную типа pthread_mutexattr_t. Эта переменная позволяет
 установить дополнительные атрибуты мьютекса. Если нам нужен обычный
 мьютекс, мы можем передать во втором параметре значение NULL. Для того
 чтобы получить исключительный доступ к некоему глобальному ресурсу, поток
 вызывает функцию pthread_mutex_lock(3), (в этом случае говорят, что «поток
 захватывает мьютекс»). Единственным параметром функции pthread_mutex_lock()
 должен быть идентификатор мьютекса. Закончив работу с глобальным ресурсом,
 поток высвобождает мьютекс с помощью функции pthread_mutex_unlock(3),
 которой также передается идентификатор мьютекса. Если поток вызовет функцию
 pthread_mutex_lock() для мьютекса, уже захваченного другим потоком, эта
 функция не вернет управление до тех пор, пока другой поток не высвободит
 мьютекс с помощью вызова pthread_mutex_unlock() (после этого мьютекс,
 естественно, перейдет во владение нового потока). Удаление мьютекса
 выполняется с помощью функции pthread_mutex_destroy(3). Стоит отметить, что в
 отличие от многих других функций, приостанавливающих работу потока, вызов
 pthread_mutex_lock() не является точкой останова. Иначе говоря, поток,
 находящийся в режиме отложенного досрочного завершения, не может быть
 завершен в тот момент, когда он ожидает выхода из pthread_mutex_lock().
 
 

28.

 Создавая новый поток, вы можете указать ряд дополнительных атрибутов,
 определяющих некоторые его параметры. Из всех этих атрибутов более всего
 востребован атрибут DETACHED, позволяющий создавать отделенные потоки. Во
 всех рассмотренных выше примерах мы использовали функцию pthread_join(),
 позволяющую дождаться завершения потока и получить значение, возвращенное
 его функцией. Для того чтобы функция pthread_join() могла получить значение
 функции потока, завершившегося до вызова pthread_join(), система сохраняет
 данные о потоке после его завершения (это похоже на появления «зомби» после
 завершения самостоятельного процесса). Если наша программа интенсивно
 работает с потоками и синхронизация потоков с помощью pthread_join() нам не
 нужна, мы можем сэкономить ресурсы системы, используя отделенные потоки.
 Отделенные потоки отличаются от обычных (присоединяемых) потоков тем, что
 после завершения отделенного потока система не сохраняет информацию о нем.
 Если вызвать функцию pthread_join() для отделенного потока, она вернет
 сообщение об ошибке.
 Вы можете превратить присоединяемый поток в отделенный с помощью вызова
 функции pthread_detach(3), однако придать потоку свойство «отделенности»
 можно и на этапе его создания, с помощью дополнительного атрибута DETACHED.
 Для того чтобы назначить потоку дополнительные атрибуты, нужно сначала
 создать объект, содержащий набор атрибутов. Этот объект создается функцией
 pthread_attr_init(3). Единственный аргумент этой функции – указатель на
 переменную типа pthread_attr_t, которая служит идентификатором набора
 атрибутов. Функция pthread_attr_init() инициализирует набор атрибутов потока
 значениями, заданными по умолчанию, так что мы можем модифицировать только
 те атрибуты, которые нас интересуют, и не беспокоиться об остальных. Для
 добавления атрибутов в набор используются специальные функции с именами
 pthread_attr_set<имя_атрибута>. Например, для того, чтобы добавить атрибут
 «отделенности», мы вызываем функцию pthread_attr_setdetachstate(3). Первым
 аргументом этой функции должен быть адрес объекта набора атрибутов, а вторым
 аргументом – константа, определяющая значение атрибута. Константа
 PTHREAD_CREATE_DETACHED указывает, что создаваемый поток должен быть
 отделенным, тогда как константа PTHREAD_CREATE_JOINABLE определяет
 создание присоединяемого (joinable) потока, который может быть
 синхронизирован функций pthread_join(3). После того, как мы добавили
 необходимые значения в набор атрибутов потока, мы вызываем функцию создания
 потока pthread_create(). Набор атрибутов потока передается в качестве второго
 аргумента этой функции.
 
 

29.

 Демонами в мире Unix традиционно называются процессы, которые не
 взаимодействуют с пользователем напрямую. У процесса-демона нет
 управляющего терминала и нет, соответственно, пользовательского
 интерфейса. Для управления демонами приходится использовать другие
 программы. Само название «демоны» возникло благодаря тому, что многие
 процессы этого типа большую часть времени проводят в ожидании какого-то
 события. Когда это событие наступает, демон активизируется (выпрыгивает,
 как чертик из табакерки), выполняет свою работу и снова засыпает в
 ожидании события. Следует отметить, что многие демоны, такие как,
 например, Web-сервер или сервер баз данных, могут отбирать на себя
 практически все процессорное время и другие ресурсы системы.
 

30.

 Каждый процесс-демон создает так называемый pid-файл (или файл
 блокировки). Этот файл обычно содержится в директории /var/run и имеет имя
 daemon.pid, где “daemon” соответствует имени демона. Файл блокировки
 содержит значение PID процесса демона. Этот файл важен по двум причинам.
 Во-первых, его наличие позволяет установить, что в системе уже запущен
 один экземпляр демона. Большинство демонов, включая наш, должны
 выполняяться не более чем в одном экземпляре (это логично, если учесть,
 что демоны часто обращаются к неразделяемым ресурсам, таким, как сетевые
 порты). Завершаясь, процесс-демон удаляет pid-файл, указывая тем самым,
 что можно запустить другой экземпляр процесса. Однако, работа демона не
 всегда завершается нормально, и тогда на диске остается pid-файл
 несуществующего процесса. Это, казалось бы, может стать непреодолимым
 препятствием для повторного запуска демона, но на самом деле, демоны
 успешно справляются с такими ситуациями. В процессе запуска демон
 проверяет наличие на диске pid-файла с соответствующим именем. Если
 такой файл существует, демон считывает из него значение PID и с помощью
 функции kill(2) проверяет, существует ли в системе процесс с указанным PID.
 Если процесс существует, значит, пользователь пытается запустить демон
 повторно. В этом случае программа выводит соответствующее сообщение и
 завершается. Если процесса с указанным PID в системе нет, значит pid-файл
 принадлежал аварийного завершенному демону. В этой ситуации программа
 обычно советует пользователю удалить pid-файл (ответственность в таких
 делах всегда лучше переложить на пользователя) и попытаться запустить ее
 еще раз. Может, конечно, случиться и так, что после аварийного завершения
 демона на диске останется его pid-файл, а затем какой-то другой процесс
 получит тот же самый PID, что был у демона. В этой ситуации для вновь
 запускаемого демона все будет выглядеть так, как будто его копия уже
 работает в системе, и запустить демон повторно вы не сможете. К счастью,
 описанная ситуация крайне маловероятна.
 Вторая причина, по которой файл блокировки считается полезным,
 заключается в том, что с помощью этого файла мы можем быстро выяснить
 PID демона, не прибегая к команде ps.
 Далее наш демон вызывает функцию fork(3), которая создает копию его
 процесса. Родительский процесс при этом завершается:
 curPID=fork();
 switch(curPID) {
 case 0: /* мы в дочернем процессе */
 break;
 case -1: /* ошибка fork() - случилось что-то страшное */
 fprintf(stderr,"Error: initial fork failed: %s\n",
 strerror(errno));
 return -1;
 break;
 default: /* мы в родительском процессе, завершаем его */
 exit(0);
 break;
 }
 Делается это для того чтобы процесс-демон отключился от управляющего
 терминала. С каждым терминалом Unix связан набор групп процессов,
 именуемый сессией. В каждый момент времени только одна из групп
 процессов, входящих в сессию, имеет доступ к терминалу (то есть, может
 выполнять ввод/вывод с помощью терминала). Эта группа именуется
 foreground (приоритетной). В каждой сессии есть процесс-родоначальник,
 который называется лидером сессии. Если процесс-демон запущен с консоли,
 он, естественно, становится частью приоритетной группы процессов,
 входящих в сессию соответствующего терминала.
 Для того чтобы отключиться от терминала, демон должен начать новую
 сессию, не связанную с каким-либо терминалом. Для того чтобы демон мог
 начать новую сессию, он сам не должен быть лидером какой-либо другой
 сессии. Вызов fork() создает дочерний процесс, который заведомо не является
 лидером сессии. Далее дочерний процесс, полученный с помощью fork(),
 начинает новую сессию с помощью вызова функции setsid(2). При этом
 процесс становится лидером (и единственным участником) новой сессии.
 if(setsid()<0)
 return -1;
 
 Стоит отметить, что теперь наш демон получил новый PID, который мы
 снова должны записать в pid-файл демона. Мы записываем значение PID в
 файл в строковом виде (а не как переменную типа pid_t). Делается это для
 удобства пользователя, чтобы значение PID из pid-файла можно было
 прочитать с помощью cat. Например:
 kill `cat /var/run/aahzd.pid `
 Нашему демону удалось разорвать связь с терминалом, с которого он был
 запущен, но он все еще может быть связан с другими процессами и
 файловыми системами через файловые дескрипторы, унаследованные от
 родительских процессов. Для того чтобы разорвать и эту связь, мы закрываем
 все файловые дескрипторы, открытые в нашем процессе:
 numFiles = sysconf(_SC_OPEN_MAX);
 for(i = numFiles-1; i >= 0; --i) {
 if(i != lockFD)
 close(i);
 }
 Функция sysconf() с параметром _SC_OPEN_MAX возвращает максимально
 возможное количество дескрипторов, которые может открыть наша
 программа. Мы вызываем функцию close() для каждого дескриптора
 (независимо от того, открыт он или нет), за исключением дескриптора pid-
 файла, который должен оставаться открытым.
 Во время работы демона дескрипторы стандартных потоков ввода, вывода и
 ошибок также должны быть открыты, поскольку они необходимы многим
 функциям стандартной библиотеки. В то же время, эти дескприторы не
 должны указывать на какие-либо реальные потоки ввода/вывода. Для того,
 чтобы решить эту задачу, мы закрываем первые три дескриптора, а затем
 снова открываем их, указывая в качестве имени файла /dev/null:
 stdioFD = open("/dev/null", O_RDWR);
 dup(stdioFD);
 dup(stdioFD);
 Теперь мы можем быть уверены, что демон не получит доступа к какому-
 либо терминалу. Тем не менее, у демона должна быть возможность выводить
 куда-то сообщения о своей работе. Традиционно для этого используются
 файлы журналов (log-файлы). Файлы журналов для демона подобны черным
 ящикам самолетов. Если в работе демона произошел какой-то сбой,
 пользователь может проанализировать файл журнала и (при определенном
 везении) установить причину сбоя. Ничто не мешает нашему демону открыть
 свой собственный файл журнала, но это не очень удобно. Большинство
 демонов пользуются услугами утилиты syslog, ведущей журналы множества
 системных событий. Мы открываем доступ к журналу syslog с помощью
 функции openlog(3):
 openlog(logPrefix, LOG_PID|LOG_CONS|LOG_NDELAY|LOG_NOWAIT, LOG_LOCAL0);
 (void) setlogmask(LOG_UPTO(logLevel));
 Первый параметр функции openlog() – префикс, который будет добавляться
 к каждой записи в системном журнале. Далее следуют различные опции
 syslog. Функция setlogmask(3) позволяет установить уровень приоритета
 сообщений, которые записываются в журнал событий. При вызове функции
 BecomeDaemonProcess() мы передаем в параметре logLevel значение
 LOG_DEBUG. В сочетании с макросом LOG_UPTO это означает, что в журнал
 будут записываться все сообщения с приоритетом, начиная с наивысшего и
 заканчивая LOG_DEBUG.
 Последнее, что нам нужно сделать для «демонизации» процесса – вызывать
 функцию
 setpgrp();
 Этот вызов создает новую группу процессов, идентификатором которой
 является идентификатор текущего процесса.
 
 

31.

 Структура termios позволяет управлять флагами и численными
 параметрами, которые можно разделить на пять групп: ввод, вывод,
 управление оборудованием, локальные параметры и специальные
 управляющие символы. Простейшая структура termios состоит из пяти полей,
 соответствующих перечисленным группам:
 struct termios {
 tcflag_t c_iflag; // флаги управления вводом
 tcflag_t c_oflag; // флаги управления выводом
 tcflag_t c_cflag; // флаги управления оборудованием
 tcflag_t c_lflag; // флаги управления локальными параметрами
 cc_t c_cc[NCCS] // Специальные управляющие символы
 };
 У структуры termios могут быть и другие поля, но нас они не интересуют.
 Обычно работа со структурой termios происходит по следующему сценарию
 (все необходимые функции и типы данных определены в файле termios.h): с
 помощью функции tcgetattr(3) мы получаем копию структуры, описывающую
 текущее состояние терминала и делаем еще одну копию. Затем мы
 модифицируем значения полей одной из копий termios, так чтобы изменить
 нужные нам параметры терминала, и передаем системе новое значение
 termios с помощью функции tcsetattr(3). После того, как работа с терминалом
 в измененном режиме закончена, мы восстанавливаем исходное состояние
 терминала с помощью сохраненной копии исходной структуры termios и
 функции tcsetattr(). Первым аргументом функции tcgetattr() должен быть
 дескриптор файл, соответствующего терминалу. Вторым аргументом является
 указатель на структуру termios, в которой функция возвращает текущие
 настройки терминала. Первым параметром функции tcsetattr() также служит
 дескриптор файла терминала. Второй параметр используется для передачи
 флагов, определяющих, когда изменения параметров терминала должны
 вступить в силу. Третьим параметром tcsetattr() является указатель на
 структуру termios, содержащую новые параметры.
 Ключевой момент во всем этом, – модификация полей структуры termios.
 Первые четыре поля структуры содержат комбинации флагов, определяющих
 параметры терминала. Пятое поле представляет собой массив значений.
 Индексам этого массива соответствуют специальные константы, с помощью
 которых мы можем понять значение элементов массива. Рассмотрим сначала
 поля termios, содержащие флаги. Полное описание флагов (а их довольно
 много) вы найдете на странице man, посвященной termios. Я перечислю здесь
 только некоторые флаги, которые устанавливаются в поле c_lflag, поскольку
 они представляются мне наиболее интересными. Флаг ECHO управляет
 отображением вводимых символов на экране монитора. Если он установлен,
 символы отображаются, в противном случае – нет. Флаг ECHOE делает то же,
 что и флаг ECHO, но только для управляющих символов, стирающих другие
 символы или строки (например, BackSpace). Поскольку неканонический
 режим не поддерживает редактирование строки, в этом режиме флаг ECHOE
 игнорируется. Если установлен флаг ICANON, терминал находится в
 каноническом режиме, в противном случае – в неканоническом. Флаг IEXTEN
 переводит терминал в режим расширенной обработки вводимых символов. От
 того, установлен ли флаг ISIG, зависит, будут ли специальные комбинации
 клавиш, такие как Ctrl-C и Ctrl-Z, инициировать соответствующие им сигналы.
 
 

32.

 #include < stdio.h>
 #include < stdlib.h>
 #include < signal.h>
 #include < termios.h>
 #define BUF_SIZE 15
 int main (int argc, char ** argv)
 { struct termios oldsettings, newsettings;
 char password[BUF_SIZE+1];
 int len;
 sigset_t newsigset, oldsigset;
 sigemptyset(&newsigset);
 sigaddset(&newsigset, SIGINT);
 sigaddset(&newsigset, SIGTSTP);
 sigprocmask(SIG_BLOCK, &newsigset, &oldsigset);
 tcgetattr(fileno(stdin), &oldsettings);
 newsettings = oldsettings;
 newsettings.c_lflag &= ~ECHO;
 tcsetattr(fileno(stdin), TCSAFLUSH, &newsettings);
 printf("Enter password and press [Enter]\n");
 len = read(fileno(stdin), password, BUF_SIZE);
 password[len] = 0;
 tcsetattr(fileno(stdin), TCSANOW, &oldsettings);
 sigprocmask(SIG_SETMASK, &oldsigset, NULL);
 printf("Your password is %s\n", password);
 return EXIT_SUCCESS;
 }
 В начале программы мы блокируем сигналы SIGINT и SIGTSTP (зачем это
 нужно, я объясню ниже). Затем, с помощью функции tcgetattr() мы заполняем
 переменную oldsettings типа struct termios текущими значениями параметров
 терминала. Далее мы копируем содержимое oldsettings в переменную
 newsettings. Строка
 newsettings.c_lflag &= ~ECHO;
 Сбрасывает флаг ECHO в структуре newsettings. Остальные параметры
 терминала остаются без изменений. Далее, с помощью функции tcsetattr() мы
 устанавливаем новые параметры. Теперь терминал не будет выводить на
 экран символы, вводимые пользователем, и мы можем вызвать функцию,
 считывающую значение пароля. После этого программа восстанавливает
 прежнее состояние терминала. Теперь мы можем разблокировать
 заблокированные сигналы. Мы распечатываем строку с введенным «паролем»
 (не вздумайте вводить в программе какой-нибудь настоящий пароль, иначе
 злоумышленник, прячущийся за вашей спиной, обязательно его увидит).
 Зачем мы блокировали сигналы вона время ввода пароля? Представьте
 себе, что в то время, когда программа ожидает ввода пароля, пользователь
 передумал и захотел завершить ее с помощью Ctrl-C. Если программа
 завершится в этот момент, состояние терминала не будет восстановлено, и
 символы, вводимые пользователем, по-прежнему не будут отображаться. Это
 не смертельно, но неудобно. Вот почему программы, ожидающие ввода
 пароля, временно блокируют некоторые сигналы.
 
 

33.

 Специально для того, чтобы предоставить интерфейс «укажи и
 щелкни» пользователям текстовых терминалов, была разработана библиотека
 curses (ее название происходит от ее важнейшей функции – управления
 курсором, а вовсе не от проклятья, которая она накладывает на
 программистов). Изначально библиотека curses создавалась для BSD UNIX. В
 Linux используется открытый (на условиях MIT License) клон curses –
 библиотека ncurses (new curses). Приложений, использующих ncurses, в
 современных Linux-системах не так уж и много. Среди наиболее популярных
 проектов, основанных на ncurses, можно назвать Midnight Commander,
 текстовый Web-браузер lynx, программу чтения новостей tin и почтовый
 клиент mutt.
 

34.

 Основными концепциями пользовательского интерфейса программы,
 использующей ncurses, являются экран (screen), окно (window) и под-окно
 (sub-window). Экраном называется все пространство, на котором ncurses
 может выводить данные. С точки зрения ncurses, экран – это матрица ячеек, в
 которые можно выводить символы. Если монитор работает в текстовом
 режиме, экран ncurses совпадает с экраном монитора. Если терминал
 эмулируется графической программой, экраном является рабочая область
 окна этой программы. Окном ncurses называется прямоугольная часть экрана,
 для которой определены особые параметры вывода. В частности, размеры
 окна влияют на перенос и прокрутку строк, выводимых в этом окне. В каком-
 то смысле окно можно назвать «экраном в экране». На уровне интерфейса
 программирования окна представлены структурами данных, по этой причине
 мы будем часто говорить об окне как о структуре.
 В процессе инициализации ncurses автоматически создается окно stdscr,
 размеры которого совпадают с размерами экрана. Кроме структуры stdscr по
 умолчанию создается еще одна структура – curscr. Операции вывода данных
 ncurses модифицируют содержимое структуры stdscr, однако на экране всегда
 отображается содержимое окна curscr. Иначе говоря, данные, которые
 выводит ваша программа в окно stdscr (или в другое окно), не отображаются
 на экране монитора автоматически. Для того чтобы сделать результаты
 вывода видимыми, вы должны вызывать специальные функции обновления
 экрана (refresh() или wrefresh()). Эти функции сравнивают содержимое окон
 stdscr и curscr и на основе различий между ними вносят изменения в
 структуру curscr, а затем обновляют экран. Благодаря наличию окна curscr,
 ncurses-программе не требуется «помнить» весь свой предыдущий вывод и
 перерисовывать его всякий раз, когда в этом возникает необходимость. Этим
 программы ncurses отличаются от графических программ. В старину, когда
 терминалы связывались с компьютерами через модемы, использование двух
 окон давало дополнительное преимущество в скорости обмена данными, ведь
 программе нужно было передавать на терминал не копию экрана целиком, а
 только разницу между содержимым окон curscr и stdscr.
 Хотя ваша программа может пользоваться для вывода данных
 исключительно окном stdscr, ваша задача по проектированию интерфейса
 существенно упростится, если вы будете создавать собственные окна,
 расположенные «внутри» stdscr. Программа, использующая ncurses, может
 работать с несколькими окнами одновременно, выполняя вывод в каждое из
 них. Кроме окон (windows) программы ncurses могут создавать под-окна
 (subwindows), поведение которых несколько отличается от поведения
 стандартных окон.
 Важнейшей особенностью ncurses является возможность указать
 произвольную позицию курсора для вывода (и ввода) данных. Позиция
 курсора отсчитывается от левого верхнего угла текущего окна. Ячейка в
 верхнем левом углу имеет координаты (0, 0). При работе с функциями ncurses
 важно помнить, что первой координатой является номер строки (что
 соответствует y, в терминах графического программирования), а второй
 координатой – номер столбца (что соответствует x в графическом режиме).
 В случае ошибки функции ncurses обычно возвращают константу ERR. Если
 функция не должна возвращать какое-то информативное значение (как,
 например, функция getch()), в случае успешного выполнения она возвращает
 значение OK.
 
 

35.

 Написание первой программы ncurses (она называется cursed, исходный
 текст вы найдете в файле cursed.c) мы начнем с перечисления заголовочных
 файлов.
 #include < termios.h>
 #include < sys/ioctl.h>
 #include < signal.h>
 #include < stdlib.h>
 #include < curses.h>
 Помимо уже знакомых нам заголовочных файлов, в программу включен
 файл , который содержит объявления функций, переменных,
 констант и структур данных, экспортируемых библиотекой ncurses.
 Прежде чем переходить к программированию ncurses, следует рассмотреть
 решение одной задачи, с которой в настоящее время сталкиваются все
 разработчики, использующие эту библиотеку. Речь идет об изменении
 размеров окна терминала (под размерами окна в данном случае понимается
 число строк и столбцов). Пользователи настоящих текстовых терминалов
 редко переключали их режимы, и готовы были мириться с последствиями
 своих действий. В наши дни, когда экраном терминала зачастую служит окно
 графической программы, пользователь вправе ожидать, что при изменении
 размеров окна работа консольной программы не нарушится, а ее интерфейс
 не развалится.
 Когда размеры окна терминала меняются, выполняющаяся в нем
 программа получает сигнал SIGWINCH. Это одновременно и хорошо и плохо.
 Хорошо – потому, что терминал информирует программу об изменении своих
 размеров, плохо – потому, что сигналы имеют особенность вмешиваться в
 работу программы. Например, если вы напишете программу, использующую
 ncurses, и не позаботитесь об обработке сигнала SIGWINCH, при изменении
 размеров окна терминала ваша программа может неожиданно завершиться,
 оставив терминал в неканоническом состоянии. Рассмотрим, как
 обрабатывается сигнал SIG_WINCH в программе cursed.
 void sig_winch(int signo)
 { struct winsize size;
 ioctl(fileno(stdout), TIOCGWINSZ, (char *) &size);
 resizeterm(size.ws_row, size.ws_col);
 }
 Функция sig_winch() представляет собой обработчик сигнала SIGWINCH.
 Следует отметить, что изменение размеров окна программы, работающей в
 текстовом режиме, представляет собой довольно нетривиальную задачу и
 стандартного рецепта, описывающего, что должна делать программа, когда
 размеры окна изменились, не существует. Разработчики ncurses, как могли,
 постарались упростить решение этой задачи для программистов, введя
 функцию resizeterm(). Функцию resizeterm() следует вызывать сразу после
 изменения размеров окна терминала. Аргументами функции resizeterm()
 должны быть новые размеры экрана, заданные в строках и столбцах. Функция
 resizeterm() старается сохранить внешний вид и порядок работы приложения
 в изменившемся окне терминала, но это ей удается не всегда, с чем мы
 столкнемся ниже. Необходимые для resizeterm() значения размеров окна мы
 получаем с помощью специального вызова ioctl(). При этом первым
 параметром функции ioctl() должен быть дескриптор файла устройства,
 представляющего терминал. Вторым параметром ioctl() является константа
 TIOCGWINSZ, а третьим параметром – адрес структуры winsize. Структура
 winsize определенная в файле , включает в себя поля ws_row и
 ws_col, в которых возвращается число строк и столбцов окна терминала.
 Перейдем теперь к функции main() программы cursed:
 int main(int argc, char ** argv)
 { initscr();
 signal(SIGWINCH, sig_winch);
 cbreak();
 noecho();
 curs_set(0);
 attron(A_BOLD);
 move(5, 15);
 printw("Hello, brave new curses world!\n");
 attroff(A_BOLD);
 attron(A_BLINK);
 move(7, 16);
 printw("Press any key to continue...");
 refresh();
 getch();
 endwin();
 exit(EXIT_SUCCESS);
 }
 Работа с ncurses начинается с вызова функции initscr(). Эта функции
 инициализирует структуры дынных ncurses и переводит терминал в нужный
 режим. По окончании работы с ncurses следует вызвать функцию endwin(),
 которая восстанавливает то состояние, в котором терминал находился до
 инициализации ncurses. После вызова initscr() мы устанавливаем обработчик
 сигнал SIGWINCH. Устанавливать обработчик SIGWINCH следует только
 после инициализации ncurses, поскольку в обработчике используется функция
 resizeterm(), предполагающая, что библиотека ncurses уже инициализирована.
 Функция noecho() отключает отображение символов, вводимых с клавиатуры.
 Функция cur_set() управляет видимостью курсора. Если вызвать эту функцию с
 параметром 0, курсор станет невидимым, вызов же функции с ненулевым
 параметром снова «включает» курсор.
 Функция attron() позволяет указать некоторые дополнительные атрибуты
 выводимого текста. Этой функции можно передать одну или несколько
 констант, обозначающих атрибуты (в последнем случае их следует объединить
 с помощью операции «|»). Например, атрибут A_UNDERLINE включает
 подчеркивание текста, атрибут A_REVERSE меняет местами цвет фона и
 текста, атрибут A_BLINK делает текст мигающим, атрибут A_DIM снижает
 яркость текста по сравнению с нормальной, атрибут A_BOLD делает текст
 жирным в монохромном режиме и управляет яркостью цвета в цветном
 режиме работы монитора. Специальный атрибут COLOR_PAIR() применяется
 для установки цветов фона и текста. На странице man, посвященной функции
 attron(), вы найдете описания и других атрибутов. Все перечисленные выше
 атрибуты оказывают воздействие только на тот текст, который выводится
 после установки атрибута. Выше мы говорили о том, что окно ncurses
 представляет собой матрицу ячеек для вывода символов. Помимо кода
 символа каждая ячейка содержит дополнительные атрибуты символа.
 Сбросить атрибуты можно с помощью функции attroff(). Так же, как и в случае
 с attron(), функции attroff() можно передать несколько констант,
 обозначающих атрибуты, разделенных символом «|». Так же, как и установка
 атрибута, сброс атрибута влияет только на текст, напечатанный после сброса
 (текст, напечатанный ранее с установленным атрибутом, остается без
 изменений). В нашей программе мы сначала устанавливаем атрибут A_BOLD.
 Теперь, до тех пор, пока мы не сбросим этот атрибут, весь текст будет
 печататься жирным шрифтом. Прежде чем напечатать строку текста этим
 шрифтом, мы воспользуемся еще одной возможностью ncurses – выводом
 текста в произвольной области экрана. Функция move() устанавливает
 позицию курсора в окне stdscr. Первый аргумент функции – строка, второй
 аргумент – столбец, в котором должен находиться курсор. Следующий затем
 вывод текста начнется с той позиции, в которой был установлен курсор. Если
 попытаться поместить курсор за пределы окна, функция move() не станет
 выполнять никаких действий, и курсор останется на прежнем месте. Мы
 переводим курсор в позицию (5, 15) и выводим на экран строку "Hello, brave
 new curses world!" с помощью функции printw(). Функция printw()
 представляет собой аналог printf() для окна stdscr и имеет тот же список
 параметров, что и printf().
 Теперь мы сбрасываем атрибут A_BOLD с помощью функции attroff(),
 устанавливаем атрибут A_BLINK, переводим курсор в позицию (7,16) и
 распечатываем строку "Press any key to continue...". Хотя мы уже напечатали
 две строки, на экране терминала все еще ничего нет. Для того чтобы
 напечатанные нами символы стали видимыми, необходимо вызывать функцию
 refresh(). Функция refresh() является, в некотором роде, избыточной
 (действительно, почему бы не отображать распечатанный текст сразу же
 после вызова printw()?). Фактически функция refresh() представляет собой
 пережиток тех времен, когда терминал связывался с компьютером при
 помощи модема. Контролируя частоту вызовов refresh(), можно было
 сократить трафик между терминалом и компьютером (см. выше описание
 взаимодействия окон stdscr и curscr).
 
 

36.

 int main(int argc, char ** argv)
 { WINDOW *
 wnd;
 WINDOW * subwnd;
 initscr();
 signal(SIGWINCH, sig_winch);
 cbreak();
 curs_set(0);
 refresh();
 wnd = newwin(6, 18, 2, 4);
 box(wnd, '|', '-');
 subwnd = derwin(wnd, 4, 16, 1, 1);
 wprintw(subwnd, "Hello, brave new curses world!\n");
 wrefresh(wnd);
 delwin(subwnd);
 delwin(wnd);
 move(9, 0);
 printw("Press any key to continue...");
 refresh();
 getch();
 endwin();
 exit(EXIT_SUCCESS);
 }
 В функции main() мы, как и прежде, инициализируем ncurses с помощью
 функции initscr() и устанавливаем обработчик SIGWINCH. Далее мы
 делаем курсор невидимым, как и в предыдущем примере. После этого
 мы должны обновить экран с помощью refresh().
 Мы создаем новое окно с помощью функции newwin(). Наше окно
 насчитывает 6 строк и 18 столбцов и его верхний левый угол находится в
 ячейке (2, 4) окна stdscr. Указатель на структуру WINDOW, который
 возвращает функция newwin(), мы сохраняем в переменной wnd. Функция
 box(), которую мы вызываем далее, позволяет создать рамку вдоль границы
 окна. Аргументами этой функции должны быть идентификатор окна и
 символы, используемые, соответственно, для рисования вертикальной и
 горизонтальной границы. Теперь было бы логично вывести какой-нибудь текст
 в окно, обрамленное рамкой, но тут возникает одна сложность. Поскольку
 символы рамки сами находятся внутри окна, символы текста могут затереть
 их в процессе вывода. Мы решаем эту проблему с помощью создания под-окна
 subwnd внутри окна wnd и вывода текста в это под-окно. Поскольку окно
 subwnd по размерам меньше, чем окно wnd, символы рамки не будут стерты.
 Теперь мы можем распечатать текст, что мы и делаем с помощью функции
 wprintw(), указав ей идентификатор окна subwnd. Для того чтобы символы,
 напечатанные в окне, стали видимыми, мы должны вызвать функцию
 wrefresh(). Мы вызываем эту функцию только для окна wnd, поскольку оно
 содержит символьный массив и своего под-окна subwnd. Обратите внимание
 на то, что символы строки "Hello, brave new curses world!", которую мы
 печатаем в под-окне subwnd с помощью функции wprintw(), переносятся при
 достижении границы под-окна (рис. 2). После завершения работы с окнами
 мы можем удалить структуры wnd и subwnd с помощью функции delwin().
 Весь вывод, выполненный в окне wnd, останется на экране (точнее в окне
 curscr) до тех пор, пока вы не перезапишете его другим выводом.
 
 

37.

 Библиотека ncurses инициализирует восемь базовых цветов: черный, красный, зеленый,
 желтый, синий (blue), ярко-красный (magenta), голубой (cyan) и белый
 (базовыми называются цвета с обычным уровнем яркости). Поскольку к
 каждому базовому цвету можно применить атрибут повышенной яркости
 A_BOLD, мы получаем всего 16 цветов (в результате применения атрибута
 A_BOLD к черному цвету получается темно-серый цвет). Базовым цветам
 соответствуют константы COLOR_BLACK, COLOR_RED, COLOR_GREEN,
 COLOR_YELLLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN и
 COLOR_WHITE (для черного, красного, зеленого, желтого, синего, ярко-
 красного, голубого и белого цветов соответственно). Следует отметить, что
 фактические цвета в окне терминала зависят, прежде всего, от настроек
 самого терминала. Например, базовый желтый цвет (COLOR_YELLLOW) будет
 выглядеть скорее как коричневый, а для того, чтобы он стал, собственно,
 желтым, ему необходимо придать атрибут повышенной яркости. Библиотека
 ncursese позволяет определять собственные цвета с помощью функции
 init_color, но эта возможность поддерживается не всеми консолями.
 Позволяет ли консоль определять собственные цвета, можно выяснить с
 помощью функции can_change_color(). Цвета в ncurses объединяются в пары –
 цвет символов (foreground) и цвет фона (background). Перед тем как печатать
 цветной текст, необходимо определить соответствующую цветовую пару и
 установить ее в качестве атрибута текста (так же как устанавливается атрибут
 мигания или подчеркивания). Фактически номер цветовой пары является
 одним из атрибутов символа. Изменить цвет фона или цвет символов
 независимо друг от друга нельзя, необходимо определять новую пару.
 Система управления цветами ncurses инициализирует две переменные –
 COLORS (количество базовых цветов) и COLOR_PAIRS (максимальное
 количество цветовых пар, которые можно определить одновременно). При
 работе с терминалом konsole эти переменные принимают значения 8 и 64
 соответственно.
 
 

38.

 Еще одной важной возможностью, которую ncurses предоставляет
 программистам, является поддержка мыши в окне терминала. Рассмотрим
 программу cursedmouse (на диске – файл cursedmouse.c), которая
 регистрирует щелчки левой кнопкой мыши, сделанные пользователем в окне
 терминала, и распечатывает координаты курсора мыши в момент щелчка.
 Ради простоты мы не создаем в этой программе никаких окон (кроме окна
 stdscr, которое создается автоматически).
 #include < termios.h>
 #include < sys/ioctl.h>
 #include < signal.h>
 #include < stdlib.h>
 #include < curses.h>
 void sig_winch(int signo)
 { struct winsize size;
 ioctl(fileno(stdout), TIOCGWINSZ, (char *) &size);
 resizeterm(size.ws_row, size.ws_col);
 nodelay(stdscr, 1);
 while (wgetch(stdscr) != ERR);
 nodelay(stdscr, 0);
 }
 int main(int argc, char ** argv)
 { initscr();
 signal(SIGWINCH, sig_winch);
 keypad(stdscr, 1);
 mousemask(BUTTON1_CLICKED, NULL);
 move(2,2);
 printw("Press the left mouse button to test mouse\n");
 printw("Press any key to quit...\n");
 refresh();
 while (wgetch(stdscr) == KEY_MOUSE) {
 MEVENT event;
 getmouse(&event);
 move(0, 0);
 printw("Mouse button pressed at %i, %i\n", event.x, event.y);
 refresh();
 move(event.y, event.x);
 }
 endwin();
 exit(EXIT_SUCCESS);
 }
 Поддержка мыши в ncurses инициализируется с помощью функции
 mousemask(). Первым параметром этой функции должна быть маска событий
 мыши, которые следует обрабатывать в программе, вторым параметром может
 быть указатель на переменную, в которой функция сохранит прежнюю маску
 событий или NULL, если прежняя маска нам не нужна. Каждому событию
 мыши в ncurses соответствует константа. Если мы хотим обрабатывать
 несколько событий мыши, при вызове функции mousemask() мы должны
 объединить соответствующие константы операцией «|». Повторный вызов
 mousemask() приведет к установке новой маски событий (вызов mousemask() с
 первым аргументом, равным 0, отключает поддержку мыши).
 Рассмотрим некоторые константы, определяющие события мыши.
 Константа BUTTON1_CLICKED соответствует щелчку левой кнопкой мыши
 (точнее говоря, - щелчку первой кнопкой; будет ли первая кнопка левой
 кнопкой мыши, зависит от настроек мыши). Константа BUTTON2_PRESSED
 указывает, что программа должна реагировать на нажатие пользователем
 второй (обычно – правой) кнопки мыши. Константа
 REPORT_MOUSE_POSITION указывает, что мы хотим отслеживать движение
 указателя мыши, а константа ALL_MOUSE_EVENTS заставляет программу
 реагировать на все события мыши (более полное описание констант событий
 вы найдете на странице man функции mousemask(3x)). В качестве
 результирующего значения функция mousemask() возвращает маску из
 выбранных нами событий, которые фактически могут быть обработаны. Если
 функция возвращает 0, значит работа с мышью в консоли не поддерживается.
 Каждый раз, когда в системе происходит одно из «наблюдаемых» событий
 мыши, в потоке ввода программы появляется специальный символ
 KEY_MOUSE. Точнее говоря, по умолчанию, в потоке ввода программы Linux
 появляется Esc-последовательность, соответствующая этому символу, так что
 в программе cursedmouse мы тоже должны вызвать функцию keypad() с
 ненулевым вторым параметром.
 После того, как мы считали из потока ввода специальный символ
 KEY_MOUSE, мы можем получить более подробную информацию о вызвавшем
 его событии мыши. Делается это с помощью функции getmouse(). Аргументом
 функции getmouse() должен быть указатель на структуру MEVENT.
 Определение структуры MEVENT выглядит следующим образом:
 typedef struct {
 short id; /* идентификатор для различения нескольких устройств */
 int x, y, z; /* координаты указателя в момент событи */
 mmask_t bstate; /* маска событий */
 } MEVENT;
 Координаты указателя возвращаются в формате строка (y), столбец (x).
 Поле bstate содержит один единственный бит, соответствующий константе
 события.
 В программе cursedmouse мы считываем поступающие во входной поток
 символы в цикле. Если во входном потоке появляется символ KEY_MOUSE,
 мы, с помощью функции getmouse(), определяем координаты указателя мыши
 в момент события и распечатываем их (мы распечатываем строку с
 координатами в левом верхнем углу экрана, а затем переводим курсор туда,
 куда указывала мышь в момент события). Появление в потоке ввода символа,
 отличного от KEY_MOUSE, приводит к завершению программы.
 Осталось обратить внимание читателя на обработку сигнала SIGWINCH в
 программе cursedmouse. Изменение размеров экрана при включенной
 поддержке мыши приведет к появлению в потоке ввода символов Esc-
 последовательности специального символа KEY_RESIZE (это еще один способ
 предупредить программу о том, что размеры экрана изменились). В
 программе cursedmouse появление в потоке ввода каких-либо кодов,
 отличных от KEY_MOUSE, вызывает завершение программы. Для того чтобы
 избежать досрочного завершения, в обработчике сигнала SIGWINCH мы
 опустошаем поток ввода с помощью функции flushinp(). Естественно, этот
 способ спасения программы от досрочного завершения годится не всегда
 (ведь в момент изменения размеров окна терминала поток ввода может
 содержать важную информацию). Все это лишний раз демонстрирует,
 насколько нетривиальной является обработка изменения размеров экрана в
 программах ncurses.
 
 

1. Выполняет системный вызов из libc . Имеет не более 5 параметров . Возвращает -1 в случае неудачи и >=0 в случае удачи.

 
 

2.

  iotcl предназначен для контроля ввода/вывода и используется для
 манипуляций с устройством через файловый дескриптор. Формат ioсtl:
 
   ioсtl(unsigned int fd, unsigned int request, unsigned long argument)
 Возвращаемое значение есть -1 в случае ошибки, 0 в случае успеха
 
Файлы делятся на обычно-регулярные и специальные , которые лежат в /dev или /proc. ioctl предназначен для работы со специальными файлами.

3.

 Linux IPC (Inter-process communication) предоставляет средства 
 для взаимодействия процессов между собой. 
 В распоряжении программистов есть несколько методов IPC:
 
     * полудуплексные каналы UNIX - pipes
     * FIFO (именованные каналы) - или named pipes
     * Очереди сообщений в стиле SYSV - queue
     * Множества (наборы) семафоров в стиле SYSV
     * Разделяемые сегменты памяти в стиле SYSV
     * Сетевые сокеты (в стиле Berkeley) (не охватывается этой документацией)
     * Полнодуплексные каналы (каналы потоков, не охватывается этой документацией)
 

4. Канал представляет собой средство связи стандартного вывода одного процесса со стандартным вводом другого. Они предоставляют метод односторонних коммуникаций (отсюда термин half-duplex) между процессами.

 Рассмотрим команду 
   ls | sort | lp
 Приведенный выше канал принимает вывод ls как ввод sort, и вывод sort за ввод lp. 
 Данные проходят через полудуплексный канал, перемещаясь (визуально) слева направо. 
 
Чтобы послать данные в канал, мы используем системный вызов write(), а чтобы получить данные из канала системный вызов read().

5.

 Нужно использовать системный вызов pipe(), у которого в качестве параметров 2 дескриптора.
   int pipe( int fd[2] );
 Вывод второго дескриптора становится вводом для первого.
 Все данные, проходящие через канал, перемещаются через ядро. 
 
 Конструкция канала - данные канала контролируются ядром :
                      in   <-----                     in
     Parent Process                 Kernel                  Child Process
                      out                    <-----   out
 
 Код
 int main(void)
 {
   int     fd[2], nbytes;
   pid_t   childpid;
   char    string[] = "Hello, world!\n";
   char    readbuffer[80];
 
   pipe(fd);
   if ((childpid = fork()) == -1)
   {
      perror("fork");
      exit(1);
   }
   if (childpid == 0)
   {
      /* закрываем  ввод pipe */
      close(fd[0]);
      /* родитель получает от потомка "string" через выход pipe */
      write(fd[1], string, strlen(string));
      exit(0);
   }
   else
   {
     /* закрываем вывод  pipe */
     close(fd[1]);
     /* потомок получает от родителя "string" через выход pipe */
     nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
     printf("Received string: %s", readbuffer);
   }
   return(0);
 }
 
 

6.

 Popen() - это обертка над pipe(). Он запускает shell и запускает аргумент как команду.
 Аргумент type указывает , на чтение канал или на запись.
 Канал закрывается через pclose().
 Примеры использования:
   popen("ls ~scottb", "r");
   popen("sort > /tmp/foo", "w");
   popen("sort | uniq | more", "w");
 
 

7.

 Атомарный операционный лимит определен в "linux/limits.h" следующим образом:
   #define PIPE_BUF 4096
 
 

8.

 Именованные каналы во многом работают так же, как и обычные каналы, 
 но все же имеют несколько заметных отличий.
 
     * Именованные каналы существуют в виде специального файла устройства в файловой системе.
     * Процессы различного происхождения могут разделять данные через такой канал.
     * Именованный канал остается в файловой системе для дальнейшего использования и после того, 
 как весь ввод/вывод сделан.
 

9.

 Мы можем прибегнуть к использованию системного вызова mknod()
    mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
  
 

10.

 В нашем примере мы будем трактовать канал как поток, открывая его fopen() и закрывая fclose(). 
  FILE *fp;
   char readbuf[80];
 
   /* Create the FIFO if it does not exist */
   umask(0);
   mknod(FIFO_FILE, S_IFIFO|0666, 0);
   while(1)
   {
     fp = fopen(FIFO_FILE, "r");
     fgets(readbuf, 80, fp);
     printf("Received string: %s\n", readbuf);
     fclose(fp);
   }
 После открытия fifo блокируется для других процессов.
 
 

11. Очереди сообщений, семафоры , разделяемая память . У каждого такого обьекта имеется уникальный идентификатор. Для его получения нужен ключ , который генерится системным вызовом ftok() : key_t mykey; mykey = ftok("/tmp/myapp", 'a');

 
 

12. С помощью команды ipcs

 ipcs     -q:  показать только очереди сообщений
 ipcs     -s:  показать только семафоры
 ipcs     -m:  показать только разделяемую память
 ipcs --help:  для любознательных
 
 

13. Команда ipcrm удаляет объект IPC из ядра.

 ipcrm < msg | sem | shm>  < IPC ID>
 
 

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

 
 

15. msgbuf - шаблон для данных сообщения (linux/msg.h) :

 /* message buffer for msgsnd and msgrcv calls */
 struct msgbuf {
   long mtype;         /* type of message */
   char mtext[1];      /* message text */
 };
 
 Максимальный размер сообщения определен там же :
   #define MSGMAX  4056   /* <= 4056   max size of message (bytes) */
 
 

16.

 Ядро хранит сообщения в очереди структуры msg - это односвязный список. 
 Она определена в linux/msg.h:
 
 /* one msg structure for each message */
 struct msg {
   struct msg *msg_next;   /* next message on queue */
   long  msg_type;
   char *msg_spot;         /* message text address */
   short msg_ts;           /* message text size */
 };
 
 

17.

 Для очередей сообщений это структура msqid_ds. 
 Ядро создает, хранит и сопровождает образец такой структуры для каждой очереди сообщений в системе. 
 Она определена в linux/msg.h следующим образом:
 
 struct msqid_ds {
   ... 
 };
 
 

18.

 Информацию о доступе к IPC-объектам ядро хранит в структуре ipc_perm
 struct ipc_perm
 {
   key_t  key;
   ushort uid;   /* owner euid and egid */
   ushort cuid;  /* creator euid and egid */
   ... 
 };
 
 

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

 int msgget( key_t key, int msgflg );
 Первый параметр - идентификатор , второй задает 
 

20. Чтобы поставить сообщение в очередь, используйте системный вызов msgsnd():

 int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg );
 Последний параметр указывает , как поступать в случае , если очередь переполнена
 

21. Для осуществления контроля над очередью предназначен системный вызов msgсtl().

 int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
 С помощью этой команды из очереди можно извлечь структуру msqid_ds 
 и для нее поменять режим доступа 
 Можно удалить всю очередь целиком , передав в качестве 2-го параметра cmd = IPC_RMID
 

22. Поведение msgtool зависит от аргументов командной строки, что удобно для вызова из скрипта shell. Позволяет делать все что угодно, от создания, посылки и получения сообщений до редактирования прав доступа и удаления очереди.

 Передача сообщений
   msgtool s (type) "text"
 
 Прием сообщений
   msgtool r (type)
 
 Изменение режима доступа
   msgtool m (mode)
 
 Удаление сообщения
   msgtool d
 
 Примеры
   msgtool  s   1 test
   msgtool  s   5 test
   msgtool  s   1 "This is a test"
   msgtool  r   1
   msgtool  d
   msgtool  m   660
 

23.

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

24. Структура semid_ds определена в linux/sem.h:

 /* Одна структура для каждого набора семафоров */
   struct semid_ds {
     ...
   };
 
 

25.

 Каждый элемент массива имеет тип sem, который описан в linux/sem.h:
   /* One semaphore structure for each semaphore in the system. */
   struct sem {
   short   sempid;         /* pid of last operation */
   ushort  semval;         /* current value */
   ushort  semncnt;        /* num procs awaiting increase in semval */
   ushort  semzcnt;        /* num procs awaiting semval = 0 */
   };
 

26.

 Системный вызов semget() используется для того, чтобы создать
 новое множество семафоров или получить доступ к старому.
   int semget (key_t key, int nsems, int semflg);
 
 
 

27. Системный вызов semop() выполняет операции над семафором :

   int semop( int semid, struct sembuf *sops, unsigned nsops);
 Второй аргумент - массив структур-операций над семафором , третий - их число
 Эта структура-операция имеет вид :
   /* semop system call takes an array of these */
   struct sembuf {
     ushort  sem_num;        /* semaphore index in array */
     short   sem_op;         /* semaphore operation */
     short   sem_flg;        /* operation flags */
   };
   
   sem_op - это число , которое и добавляется к семафору
 
 

28. Системный вызов semctl() используется для осуществления управления множеством семафоров. Этот вызов аналогичен вызову msgctl для очередей сообщений. С его помощью можно получить , изменить , удалить семафоры из ядра , получить информацию о процессах и т.д.

   int semctl (int semid, int semnum, int cmd, union semun arg);
 
 

29. Поведение semtool() зависит от аргументов командной строки, что удобно для вызова из скрипта shell. Позволяет делать все, что угодно, от создания и манипулирования до редактирования прав доступа и удаления множества семафоров.

   Создание множества семафоров
   semtool c (number of semaphores in set)
   
   Блокировка семафора
   semtool l (semaphore number to lock)
   
   Разблокирование семафора
   semtool u (semaphore number to unlock)
   
   Изменение прав доступа
   semtool m (mode)
   
   Удаление множества семафоров
   semtool d
   
   Примеры :
   semtool  c 5
   semtool  l
   semtool  u
   semtool  m 660
   semtool  d
 
Утилита semstat выводит на экран значение каждого из семафоров множества, созданного посредством semtool.

30. Разделяемая память может быть наилучшим образом описана как отображение участка (сегмента) памяти, которая будет разделена между более чем одним процессом. Это гораздо более быстрая форма IPC, потому что здесь нет никакого посредничества (т.е. каналов, очередей сообщений и т.п.). Вместо этого, информация отображается непосредственно из сегмента памяти в адресное пространство вызывающего процесса. Сегмент может быть создан одним процессом и впоследствии использован для чтения/записи любым количеством процессов.

 
 

31. Ядро поддерживает специальную внутреннюю структуру данных для каждого разделяемого сегмента памяти, который существует врутри его адресного пространства. Такая структура имеет тип shmid_ds и определена в linux/shm.h как следующая:

   /* One shmid data structure for each shared memory segment in the system. */
   struct shmid_ds {
     ... 
   };
 
 
В ней содержится информация о доступе к сегменту, включая права доступа и информацию о создателе сегмента , размер сегмента , время изменения сегмента и т.д.

32. Чтобы создать новый разделяемый сегмент памяти или получить доступ к уже существующему, используется системный вызов shmget().

   int shmget ( key_t key, int size, int shmflg );
 
 

33. Привязка или размещение сегмента в адресном пространстве процесса - системный вызов: shmat()

   int shmat (int shmid, char *shmaddr, int shmflg);
 
 Если аргумент addr является нулем, ядро пытается найти
 нераспределенную область. Это рекомендуемый метод. 
 

34. Системный вызов: shmctl() :

   int shmctl (int shmqid, int cmd, struct shmid_ds *buf);
 
 

35. shmtool: средство командной строки для создания, чтения, записи и удаления разделяемых сегментов памяти.

   Запись строк в сегмент
   shmtool w "text"
   
   Получение строк из сегмента
   shmtool r
   
   Изменение режима доступа
   shmtool m (mode)
   
   Удаление сегмента
   shmtool d
   
   Примеры :
   shmtool  w   test
   shmtool  w   "This is a test"
   shmtool  r
   shmtool  d
   shmtool  m   660
 
 

36. termcap - таблица элементов описания работы с терминалом в ASCII-файле /etc/termcap. Здесь вы можете найти информацию о том, как выводить специальные символы, как осуществлять операции (удаления, вставки символов или строк и т.д.) и как инициализировать терминал. Имеются библиотечные функции для чтения и использования возможностей терминала (смотри termcap(3x)).

База данных TERMinal INFOrmation построена над базой данных termcap и описывает некоторые возможности терминалов на более высоком уровне. С terminfo программа может легко менять атрибуты экрана, используя специальные клавиши, такие как функциональные клавиши и др. Эта база данных может быть найдена в /usr/.../terminfo/[A-z,0-9]*.

Библиотека (BSD-)curses дает вам высокоуровневый доступ к терминалу, базируясь на terminfo. curses позволяет открывать и манипулировать окнами на экране, предоставляет весь необходимый набор функций ввода/вывода.

ncurses представляет собой развитие curses. В версии 1.8.6 она должна быть совместима с AT&T curses, как это определено в SYSVR4, и иметь несколько расширений, таких как манипулирование цветами, специальная оптимизация для вывода, оптимизации, зависящие от терминала, и др.

37.

 Следующие переменные и константы, определенные в ncurses.h:
     * WINDOW *curscr: текущий экран
     * WINDOW *stdscr: стандартный экран
     * int LINES: строки на терминале
     * int COLS: колонки на терминале
     * bool TRUE: true flag, 1
     * bool FALSE: false flag, 0
     * int ERR: error flag, -1
     * int OK: ok flag, 0
 
 Автоматически подключатся stdio.h, stdarg.h, termios.h и unctrl.h. 
 
 Обычно программа, использующая ncurses, выглядит так:
 
 #include 
 ...
 main()
 {
    ...
    initscr();
    /* вызов функции ncurses */
    endwin();
    ...
 }
 initscr() используется для инициализации структур данных ncurses и для чтения файла terminfo. 
 Будет захвачена память под stdscr и curscr. 
 Кроме этого, экран будет очищен и будут проинициализированы LINES и COLS.
 endwin() очистит все выделенные ресурсы ncurses и восстановит режимы tty, какими они были до вызова initscr(). 
 Функция endwin() должна вызываться перед перед выходом из вашей программы.
 
 Библиотека ncurses находится в /usr/lib. Существует 3 версии библиотеки:
 
     * libncurses.a обычная ncurses
     * libdcurses.a ncurses для отладки
     * libpcurses.a ncurses для профилирования 
 
 Чтобы оптимально обновить физический терминал, ncurses имеет другое окно, curscr. 
 Это изображение, реально выводимое на экран. 
 Для отображения stdscr на экране используется функция refresh(). 
 После этого ncurses обновит curscr и физический терминал содержимым stdscr. 
 Библиотечные функции произведут внутреннюю оптимизацию для процесса обновления, 
 поэтому вы можете менять различные окна и затем обновлять экран сразу самым оптимальным способом. 
 
 

38.

 Создание окна :
   WINDOW *newwin(nlines, ncols, begy, begx)  
   
   WINDOW *mywin;
   mywin=newwin(0,0,0,0);
 
 Удаление окна :
   int delwin(win) 
 
 Перемещение окна :
   int mvwin(win, by, bx) 
 
 Рисует вложенное окно поверх в центре
   WINDOW *subwin(origwin, nlines, ncols, begy, begx)  
 
 Дублирование окна :
   WINDOW *dupwin(win)
 
 Копирование текста из одного окна в другое без пропусков :
   int overlay(win1, win2)  
 
 Копирование текста из одного окна в другое с пропусками :
   int overwrite(win1, win2)  
 
 Копирование части окна
   int copywin(win1, win2, sminrow, smincol, dminrow, dmincol,
 
 

39.

 Символьные
   int addch(ch)  
   int waddch(win, ch)  
   int mvaddch(y, x, ch)  
   int mvwaddch(win, y, x, ch)  
 Строковые
   int addstr(str)  
   int addnstr(str, n)  
   int waddstr(win, str)  
   int waddnstr(win, str, n)  
   int mvaddstr(y, x, str)  
   int mvaddnstr(y, x, str, n)  
   int mvwaddstr(win, y, x, str)  
   int mvwaddnstr(win, y, x, str, n)
 
 

40. Бордеры : int border(ls, rs, ts, bs, tl, tr, bl, br) int wborder(win, ls, rs, ts, bs, tl, tr, bl, br) int box(win, vert, hor) Линии : int vline(ch, n) int wvline(win, ch, n) int hline(ch, n) int whline(win, ch, n)

 
 

41.

 Скопируют пробелы на каждую позицию окна win или stdscr :
   int erase()  
   int werase(win) 
 
 Экран будет очищен с последующим обновлением : 
   int clear()  
   int wclear(win)  
 
 

42.

   int refresh()  
   int wrefresh(win)  
 refresh() копирует stdscr на терминал, а wrefresh(win) копирует изображение окна в stdscr 
 и затем делает curscr подобным stdscr.
 
   int wnoutrefresh(win)  
   int doupdate()  
 wnoutrefresh(win) копирует окно win только в stdscr. 
 Это означает, что вывода на терминал не производится, но виртуальный экран stdscr на самом деле 
 выглядит именно так, как того хочет программист. doupdate() произведет вывод на терминал. 
 Программа может менять различные окна, вызывая wnoutrefresh(win) для каждого окна, 
 а затем достаточно один раз вызвать doupdate(), чтобы обновить физический экран. 
 

43.

 Создадим новый каталог для проекта kalkulc. 
 Внутри него создадим вложенный каталог src, и перенесём в каталог src все три файла от C-версии 
 нашего калькулятора. Это main.c, calculate.c, calculate.h. 
 
 Вставьте в самое начало каждого из этих трёх файлов (main.c, calculate.c и calculate.h) макрос, 
 требующий включения файла config.h.
   #ifdef HAVE_CONFIG_H
   #include 
   #endif
 
 В каталоге проекта создадим файл configure.ac со следующим текстом.
   AC_INIT(src/main.c)
   AM_CONFIG_HEADER(src/config.h)
   AM_INIT_AUTOMAKE(kalkul,0.1)
   AC_PROG_CC
   AC_PROG_CXX
   AC_PROG_INSTALL
   AC_OUTPUT(Makefile src/Makefile)
 
 Здесь же создаём файл Makefile.am с одной строчкой.
   SUBDIRS = src
 
 А внутри каталога src – ещё один Makefile.am.
   bin_PROGRAMS = kalkul
   kalkul_SOURCES = calculate.h calculate.c main.c
   kalkul_LDADD = -lm
 
 Строчка kalkul_LDADD = -lm указывает, какую библиотеку следует подключить при компиляции.
 
 Далее в командной строке набираем команды :
   aclocal
   autoconf
   touch NEWS README AUTHORS ChangeLog
   autoheader
   automake -a
   ./configure
   make
   make dist
 
 

44. libtool предназначена для генерации из исходников статических и динамических библиотек.

 Сначала создаем обьектный файл .o 
   libtool gcc -c calculate.c
 Потом создаем сами либы с расширением .a и .so
   libtool gcc -rpath /usr/local/lib -o libcalculate.la calculate.lo
 
 

44-1. Для дебага бинарный файл должен быть собран с флагом -g.

 Запуск :
   gdb имя_программы
 Затем
   run
 Для просмотра исходного кода
   list
 Поставить точку останова на строке :
   break 21
 Просмотреть все точки останова
   info breakpoints
 Показать весь стек вызываемых функций от начала программы до текущего места
   backtrace
 Распечатать значение переменной
   print Numeral
 Продолжить программу 
   continue
 Отладчик GDB позволяет прямо во время выполнения программы изменить значение любой переменной
   set SecondNumeral=4
 Удалить точки останова
   delete 1
 Пошаговый дебаг
   step
 Проход кода минуя вход в подпрограммы-функции
   next
 Возвращение из функции
   finish
 
 

45.

 Нужно подключить хидер :
   #include < gtk/gtk.h>
 
 Затем в коде : инициализируем gtk , создаем новое окно и выводим его на экран .
   int main(int argc, char *argv[] )
   {
     GtkWidget *window;
     gtk_init (&argc, &argv);
     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
     g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (on_destroy), NULL);
     gtk_widget_show (window);
     gtk_main ();
     return 0;
   }
 
 Чтобы простым способом её скомпилировать, нужно воспользоваться обычным компилятором gcc, сообщив ему, 
 что надо подключить библиотеку GTK+. 
 Для этого мы вызываем утилиту pgk-config. 
 Эта утилита ищет установленные в системе библиотеки и возвращает путь к ним. 
 В данном случае нам нужна библиотека gtk+, что мы и указываем в параметре :
   gcc main.c -o gtkdemo `pkg-config --cflags --libs gtk+-2.0`
 

46.

 Подключаем хидер
   #include 
 Далее : создаем обьет приложения и показываем окно 
   int main(int argc, char** argv)
   {
     QApplication app(argc, argv);
     QWidget wgt;
     QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
     wgt.show();
     app.setMainWidget(&wgt);
     return app.exec();
   }
 
 Дальше компиляция : программа qmake с параметром -project просматривает все файлы с исходным кодом, 
 находящиеся в текущем каталоге, создаёт новый проект (файл с расширением .pro), 
 даёт ему такое же имя, как у текущего каталога, и подключает все найденные файлы к проекту. 
 Далее, командой qmake (без параметров) создаётся Makefile. 
 
   qmake -project
   qmake
   make
 
 

1.

 • Обычные файлы. Это то, о чем большинство пользователей компьютеров думают
 как о файлах. Они служат репозиториями данных, которые могут расти до 
 необходимых размеров, и обеспечивают произвольный доступ. Файлы Unix являются
 байт-ориентированными — любое другое логическое представление является 
 результатом программных преобразований; ядро ничего не знает о них.
 • Каналы (pipes). Простейший механизм IPC в Unix. Обычно один процесс пишет
 информацию в канал в то время как другой читает из него. Каналы — это то, что
 командные оболочки используют для перенаправления ввода-вывода (например,
 Is -LR | grep notes или Is | more), и многие программы применяют 
 каналы для того, чтобы передавать свой ввод программам, запущенным в виде их
 подпроцессов. Существуют два типа каналов: именованные и неименованные.
 Неименованные каналы создаются по мере необходимости и исчезают, как 
 только читатель и писатель на концах канала закрывают его. Неименованные каналы
 называются так, потому что они не существуют в файловой системе и потому не
 имеют файловых имен1. Именованные каналы обладают именами файлов, и имя
 файла используется для того, чтобы позволить двум независимым процессам 
 общаться через канал (подобно тому, как работают сокеты доменов Unix (см. 
 главу 17)). Каналы также известны как FIFO (first-in-first-out), потому что данные
 упорядочены в манере "первым вошел — первым вышел".
 • Каталоги. Специальные файлы, которые содержат списки файлов, хранящихся
 внутри них. Старые реализации Unix позволяли программам читать и писать их
 в той же манере, что и обычные файлы. Чтобы обеспечить большую степень 
 абстракции, добавлен специальный набор системных вызовов для обеспечения ма-
 нипуляций каталогами, хотя каталоги по-прежнему открываются и закрываются
 подобно обычным файлам. Эти функции рассматриваются в главе 14.
 • Файлы устройств. Большинство физических устройств представлены в виде 
 файлов. Есть два типа файлов устройств: блочные устройства и символьные 
 устройства. Файлы блочных устройств представляют аппаратные устройства2, которые не
 могут быть прочитаны побайтно; они должны читаться блоками определенного
 размера. В Linux блочные устройства принимают специальное управление от ядра3
 и могут содержать файловые системы4. Дисковые приводы, включая CD-ROM и
 RAM-диски, являются наиболее часто используемыми блочными устройствами.
 Символьные устройства могут быть прочитаны по одному символу за раз, и ядро
 не представляет для них никаких средств кэширования или упорядочивания.
 Модемы, терминалы, принтеры, звуковые карты и мыши — все это символьные
 устройства. Традиционно к каждому из них привязана некая сущность в каталоге
 /dev, что позволяет пользовательским процессам получать доступ к ресурсам 
 устройств как к файлам.
 • Символические ссылки. Специальный тип файла, который содержит путь к 
 другому файлу. Когда открывается символическая ссылка, система расцознает ее как
 ссылку, читает ее значение и открывает файл, на который она ссылается, вместо
 самой ссылки. Когда используется значение, сохраняемое в символической 
 ссылке, говорят, что система следует по ссылке. Если не указано другое, 
 предполагается, что системные вызовы следуют по ссылкам, которые переданы им.
 • Сокеты. Подобно каналам, сокеты представляют собой каналы IPC. Они более
 гибки, чем каналы, и могут создавать IPC-каналы между процессами, 
 запущенными на разных машинах. Сокеты обсуждаются в главе 17.
 
 Единственной уникальной отличительной чертой файла является его inode (от 
 information node — информационный узел). Информационный узел файла содержит всю
 информацию о файле, включая права доступа, ассоциированные с ним, его текущий
 размер, количество имен, которые он имеет (оно может быть равно нулю, одному, 
 двадцати или больше). Существуют два тип& информационных узлов, in-core inode 
 (информационный узел в ядре) — единственный тип, о котором нам нужно заботиться; 
 каждый открытый файл в системе имеет его. Ядро отслеживает такие узлы в памяти, и они
 одинаковы для файловых систем всех типов. Другой тип узлов — on-disk inode 
 (информационный узел на диске). Каждый файл в файловой системе имеет такой узел, и его
 точная структура зависит от типа файловой системы, в которой хранится файл.
 
  Когда процесс открывает файл в файловой системе, on-disk inode загружается в 
 память и превращается в in-core inode. Когда последний модифицируется, он 
 трансформируется обратно в on-disk inode и сохраняется в файловой системе5.
 in-core inode и on-disk inode не содержат абсолютно одинаковую информацию. Так,
 например, только in-core inode отслеживает, сколько процессов в системе в данный 
 момент используют файл, ассоциированный с ним.
 Когда in-core inode и on-disk inode синхронизируются ядром, большинство 
 системных вызовов завершаются обновлением этих узлов. Когда такое происходит, мы просто
 будем говорить об обновлении узла; это подразумевает, что изменением затронуты как
 in-core inode, так и on-disk inode. Некоторые файлы (такие как неименованные 
 каналы), не имеют on-disk inode. В этом случае обновляется только in-core inode.
 

2.

 Первые три файловых дескриптора для процессов 0, 1 и 2 имеют стандартное 
 назначение. Первый, 0, известен как стандартный ввод (stdin) и является местом, откуда
 программы должны получать свой интерактивный ввод. Файловый дескриптор 1 
 называется стандартным выводом {stdout), и большая часть вывода программ должна быть
 направлена в него. Сообщения об ошибках должны направляться в стандартный поток
 ошибок {stderr), который имеет файловый дескриптор 2. Стандартная библиотека С 
 следует этим правилам, поэтому gets() и printf () используют stdin и stdout 
 соответственно, и это соглашение дает возможность командным оболочкам правильно 
 перенаправлять ввод и вывод процессов.
 Заголовочный файл < unistd.h> представляет макросы STDINFILENO, STDOUT_
 FILENO и STDERRFILENO, которые вычисляются как файловые дескрипторы stdin, 
 stdout и stderr соответственно. Использование этих символических имен делает код более
 читабельным.
 Многие из файловых операций, которые манипулируют файловыми узлами inode,
 доступны в двух формах. Первая форма принимает в качестве аргумента имя файла.
 Ядро использует этот аргумент для поиска inode файла и выполняет соответствующую
 операцию над ним (обычно это включает следование символическим ссылкам). Вторая
 форма принимает файловый дескриптор в качестве аргумента и выполняет операцию
 над inode, на который он ссылается. Эти два набора системных вызовов используют 
 похожие имена, но системные вызовы, работающие с файловыми дескрипторами, имеют
 префикс/. Например, системный вызов chmod () изменяет права доступа для файла,
 ссылка на который осуществляется по имени; f chmod () устанавливает права доступа к
 файлу, ссылаясь на него по указанному файловому дескриптору.
 

3.

    int fd;
    fd = open("hw", 0_TRUNC | 0_CREAT | 0_WRONLY, 0644));
    /* магическое число 18 - это кол-во символов, которые будут записаны */
    if(write(fd, "Добро пожаловать!\n", 18) != 18) 
     perror("write");
      close(fd);
 
 

4.

 Файлы Unix можно разделить на две категории: просматриваемые (seekable) и не-
 просматриваемые (nonseekable)8. Непросматриваемые файлы представляют собой 
 каналы, работающие в режиме "первый вошел— первый вышел" (FIFO), которые не
 поддерживают произвольное чтение или запись, их данные не могут быть перечитаны
 или перезаписаны. Просматриваемые файлы позволяют читать и писать в произвольное
 место файла. Каналы и сокеты являются не просматриваемыми файлами; блоковые 
 устройства и обычные файлы являются просматриваемыми.
 Поскольку FIFO — это непросматриваемые файлы, то, очевидно, что read () читает
 их с начала, a write () пишет в конец. С другой стороны, просматриваемые файлы не
 имеют очевидной точки для этих операций. Вместо этого здесь вводится понятие 
 "текущего" положения, которое перемещается вперед после операции. Когда 
 просматриваемый файл изначально открывается, текущее положение устанавливается в его начало,
 со смещением 0. Если затем из него читается 10 байт, то текущее положение 
 перемещается в точку со смещением 10 от начала/
  Файлы, открытые с флагом OAPPEND ведут себя несколько иначе. Для таких файлов 
 текущая позиция перемещается в конец файла перед тем, как ядро осуществит в него запись.
 После записи текущая позиция перемещается в конец записанных данных, как обычно.
 Для файлов, работающих в режиме только для добавления, это гарантирует, что текущая
 позиция файла всегда будет находиться в его конце немедленно после вызова write ().
 
 Приложения, которые хотят читать и писать данные с произвольного места файла,
 должны установить текущую позицию перед выполнением операции чтения или записи
 данных, используя lseek ().
   int lseek(int fd, off_t offset, int whence);
 Текущая позиция для файла fd перемещается на offset байт относительно whence,
 где whence принимает одно из следующих значений:
   SEEKSET10 Начало файла.
   SEEK_CUR Текущая позиция в файле.
   SEEKEND Конец файла.
 Для SEEKCUR и SEEKEND значение offset может быть отрицательным. В этом 
 случае текущая позиция перемещается в сторону начала файла (от whence), а не в сторону
 конца. Например, приведенный ниже код перемещает текущую позицию на 5 байт 
 назад от конца файла.
   lseek(fd, -5, SEEK_END);
 
 В большинстве систем POSIX процессам разрешается перемещать текущую позицию
 за конец файла. При этом файл увеличивается до соответствующего размера и его 
 текущая позиция устанавливается в новый конец файла. Единственной ловушкой может
 быть то, что большинство систем при этом не выделяют никакого дискового 
 пространства для той части файла, которая не была записана — они просто изменяют 
 логический размер файла.
 Части файлов, которые "создаются" подобным образом, называют "дырками" (holes).
 Чтение из такой "дырки" в файле возвращает буфер, полный двоичных нулей, а запись
 в них может завершиться ошибкой по причине отсутствия свободного пространства на
 диске. Все это значит, что вызов lseek () не должен применяться для резервирования
 дискового пространства для позднейшего использования, поскольку такое 
 пространство может быть и не выделено. Если ваше приложение нуждается в выделении 
 некоторого дискового пространства для последующего использования, вы должны применять
 write (). Файлы с "дырками" часто используют для хранения редко расположенных в
 них данных, таких как файлы, представляющие хеш-таблицы.
 
 
 

5.

 /* Пока есть данные на стандартном входе (fd 0) , копировать их в
 стандартный выход (fd 1) . Выйти, когда не будет доступных данных. */
   int main(void) {
     char buf [1024];
     int len;
   /* len будет >= О, пока доступны данные   и read() успешен */
     while((len = read(STDIN_FILENO, buf, sizeof(buf))) > 0) 
     {
       if (write A, buf, len) != len) 
       {
         perror("write");
         return 1;
       }
     }
   /* len будет <= 0; если len = 0, больше нетдоступных данных. Иначе - ошибка. */
     if (len < 0) 
     {
       perror("read");
       return 1;
     }
     return 0;
   }
 

6.

  Хотя режим файла представляет всю информацию, которая может понадобиться 
 программе, для определения того, имеет ли она доступ к файлу, тестирование набора прав —
 дело хитрое и чреватое ошибкам. Поскольку ядро ухе включает в себя код для проверки
 прав доступа, предусмотрен простой системный вызов, который позволяет программам
 определять, могут ли они получить доступ к файлу определенным образом.
   #include < unistd.h>
 int access (const char *pathname, int mode);
 mode — это маска, которая содержит одно или более перечисленных ниже 
 значений.
 
 FOK  - Файл существует. Это требует прав на выполнение по всем каталогам, 
 составляющим путь, поэтому может закончиться сбоем, даже если файл существует.
 
 ROK  - Процесс может читать файл.
 
 WOK  - Процесс может писать файл.
 
 ХОК  - Процесс может исполнять файл (или искать в каталоге).
 access () возвращает 0, если указанный режим доступа разрешен, в противном 
 случае возвращает ошибку EACCESS.
 

7.

  Точно так же, как права доступа, информация о группе и владельце файла хранится
 в inode, поэтому все жесткие ссылки на файл имеют одинакового владельца и группу.
 Похожий системный вызов используется для смены владельца и группы файла.
   int chown(const char *pathname, uid__t owner, gid_t group);
   int fchown(int fd, uid_t owner, gid_t group);
 Параметры owner и group указывают нового владельца и группу для файла. Если
 любой из них равен -1, соответствующее значение не изменяется. Только пользователь
 root имеет право сменить владельца файла. Когда владелец файла меняется или файл
 записывается, то бит setuid для этого файла всегда очищается из соображений 
 безопасности. Как root, так и владелец файла могут менять группу, которая владеет файлом, но
 при условии, что владелец сам является членом этой группы. Если у файла установлен
 бит выполнения для группы, то бит setgid очищается из тех же соображений 
 безопасности. Если же бит выполнения для группы не установлен, то у файла включена 
 принудительная блокировка и режим предохраняется.
 

8.

 Когда множество имен файлов в файловой системе ссылаются на единственный
 inode, такие файлы называют жесткими ссылками (hard links) на него. Все эти 
 имена должны располагаться на одном физическом носителе (обычно это значит, что они
 должны быть на одном устройстве). Когда файл имеет множество жестких ссылок, все
 они равны — нет способа узнать, с каким именем первоначально был создан файл. Одно
 из преимуществ такой модели заключается в том, что удаление одной жесткой ссылки
 не удаляет файл с устройства — он остается до тех пор, пока все ссылки на него не будут
 удалены. Системный вызов link () связывает новое имя файла с существующим inode.
 
 Символические ссылки — это более гибкий тип ссылок, чем жесткие, но они не
 дают равноправного доступа к файлу, как это делают жесткие. В то время как жесткие
 ссылки разделяют один и тот же inode, символические ссылки просто указывают на
 другие имена файлов. Если файл, на который указывает символическая ссылка, 
 удаляется, то она указывает на несуществующий файл, что приводит к появлению висячих
 ссылок. Использование символических ссылок между подкаталогами — обычная 
 практика, и они могут также пересекать границы физических систем, чего не могут жесткие
 ссылки.
 Символически ссылки создаются почти так же, как жесткие, но при этом 
 используется системный вызов symlink ().
   int symlink(const char *origpath, const char *newpath);
 
 

9.

 Иногда процессам требуется создать новый файловый дескриптор, который 
 ссылается на ранее открытый файл. Командные оболочки используют эту функциональность
 для перенаправления стандартного ввода, вывода и потока ошибок по запросу 
 пользователя. Если процессу не важно, какой файловый дескриптор будет использован для
 новой ссылки, он должен использовать dup ().
   int dup(int oldfd);
 dup () возвращает файловый дескриптор, который ссылается на тот же inode, что и
 oldfd, или -1 в случае ошибки, oldfd остается корректным дескриптором, 
 по-прежнему ссылающимся на исходный файл. Новый файловый дескриптор — это всегда
 наименьший доступный файловый дескриптор. Если процессу нужно получить новый
 файловый дескриптор с определенным значением (например, 0, чтобы перенаправить
 стандартный ввод), то он должен использовать dup2 ().
   int dup2(int oldfd6 int newfd);
 Если newf d ссылается на уже открытый дескриптор, то он закрывается. Если вызов
 завершен успешно, он возвращает новый файловый дескриптор и newf d ссылается на
 тот же файл, что oldfd. Системный вызов fcntl () представляет почти ту же 
 функциональность командой FDUPFLD. Первый аргумент — f d — это уже открытый файловый
 дескриптор. Новый файловый дескриптор — это первый доступный дескриптор, равный
 или больший, чем значение последнего аргумента fcntl (). (В этом состоит отличие от
 работы dup2 ().) Вы можете реализовать dup2 () через fcntl () следующим образом.
   int dup2(int oldfd, int newfd) {
   close (newfd) ; /* ensure new fd is available */
   return fcntl(oldfd, F_DUPFD, newfd);
   }
 Создание двух файловых дескрипторов, ссылающихся на один и тот же файл — это
 не то же самое, что открытие файла дважды. Почти все атрибуты дублированных деск-
 рипторов разделяются: они разделяют текущую позицию, режим доступа и блокировки.
 (Все это записывается в файловой структуре15, которая создается при каждом открытии
 файла. Файловый дескриптор ссылается на файловую структуру, и дескриптор, 
 возвращенный dup (), ссылается на одну и ту же структуру.) Единственный атрибут, который
 может независимо управляться в этих двух дескрипторах — это состояние "закрыть при
 выполнении". После того, как процесс вызывает fork (), то файлы, открытые в 
 родительском процессе, наследуются дочерним, и эти пары файловых дескрипторов (один в
 родительском процессе, другой — в дочернем) ведут себя так, будто файловые 
 дескрипторы были дублированы с текущей позицией и другими разделенными атрибутами.
 

10.

  Сигналы имеют четко определенный жизненный цикл: они создаются, сохраняются
 до тех пор, пока ядро не выполнит определенное действие на основе сигнала, а затем
 вызывают совершение этого действия. Создание сигнала называют по-разному: 
 поднятие (raising), генерация или посылка сигнала. Обычно процесс посылает сигнал другому
 процессу, в то время как ядро генерирует сигналы для отправки процессу. Когда процесс
 посылает сигнал самому себе, говорят, что он поднимает его. Однако эти термины 
 используются не особо согласованно.
  Между временем, когда сигнал отправлен и тем, когда он вызывает какое-то 
 действие, его называют ожидающим (pending). Это значит, что ядро знает, что сигнал должен
 быть обработан, но пока не имеет возможности сделать это. Как только сигнал 
 поступает в процесс назначения, он называется доставленным. Если доставленный сигнал 
 вызывает выполнение специального фрагмента кода (имеется в виду обработчик сигнала),
 то такой сигнал считается перехваченным. Есть разные способы, которыми процесс 
 может предотвратить асинхронную доставку сигнала, но все же обработать его (например,
 с помощью системного вызова sigwait ()). Когда такое случается, сигнал называют
 принятым.
 
 Изначально обработка сигналов была проста. Системный вызов signal () 
 использовался для того, чтобы сообщить ядру, как доставить процессу определенный сигнал.
   void * signal(int signum, void *handler);
 
 

11.

  Посылка сигналов от одного процесса другому обычно осуществляется с помощью
 системного вызова kill (). Этот системный вызов подробно обсуждался в главе 10.
 Вариантом kill () является tkill <), который не предназначен для прямого 
 использования в программах.
   int tkill(pid_t pid, int signum);
 Существуют два отличия между kill () и tkill ()8. Первое: pid должен быть 
 положительным числом; tkill () не может использоваться для отправки сигналов группам
 процессов, как это может kill (). Другое отличие позволяет обработчикам сигналов
 определять, применялся ли вызов kill () или tkill () для генерации сигнала: 
 подробности см. далее в главе.
  Функция raise (), которая представляет собой способ генерации сигналов, 
 указанный ANSI/ISO, использует системный вызов tkill () для генерации сигналов в 
 системах Linux.
   int raise(int signum);
 Функция raise () посылает текущему процессу сигнал, указанный в signum.
 
 
 

12.

  Вместо использования функции signal () (чья семантика в процессе эволюции 
 стала неправильной) POSIX-программы регистрируют обработчики сигналов с помощью
 sigaction().
   int sigaction(int signum, struct sigaction *act, struct sigaction *oact);
 Этот системный вызов устанавливает обработчик сигнала signum, как определено
 с помощью act. Если oact не равен NULL, он принимает расположение обработчика
 перед вызовом sigaction (). Если act равен NULL, текущая установка обработчика
 остается неизменной, позволяя программе получить текущее расположение, не 
 изменяя его. sigaction () возвращает 0 в случае успеха и ненулевое значение в случае
 ошибки. Ошибки случаются только если один или несколько параметров, переданных
 sigaction (), не верны.
 Обработка сигнала ядром полностью описывается структурой struct sigaction.
   struct sigaction {
   sighandler_t sa__handler;
   sigset_t sa_mask;
   int sa_flags;
   };
 sahandler — это указатель на функцию со следующим прототипом:
 void handler(int signum);
 Здесь signum устанавливается равным номеру сигнала, который является причиной
 вызова функции, sahandler может указывать на функцию этого типа либо быть 
 равным SIG_IGN ИЛИ SIG_DFL.
 

13.

  Когда программа построена преимущественно вокруг сигналов, часто 
 необходимо, чтобы она ожидала появления какого-то сигнала, прежде чем продолжать работу.
 Системный вызов pause () предоставляет простую возможность для этого.
   int pause(void);
 Функция pause () не возвращает управления до тех пор, пока сигнал не будет 
 доставлен процессу. Если зарегистрирован обработчик для этого сигнала, то он запускается
 до того, как pause () вернет управление, pause () всегда возвращает -1 и устанавливает
 errno равным EINTR.
 Системный вызов sigsuspend () предлагает альтернативный метод ожидания 
 вызова сигнала.
   int sigsuspend(const sigset_t *mask);
 Как и pause (), sigsuspend () временно приостанавливает процесс до тех пор, пока
 не будет получен сигнал (и обработан связанным с ним обработчиком, если таковой
 предусмотрен), возвращая -1 и устанавливая errno в EINTR.
 В отличие от pause (), sigsuspend () временно устанавливает маску сигналов 
 процесса в значение, находящееся по адресу, указанному в mask, на период ожидания 
 появления сигнала. Как только сигнал поступает, маска сигналов восстанавливается в то
 значение, которое она имела до вызова sigsuspend (). Это позволяет процессу ожидать
 появления определенного сигнала за счет блокирования всех остальных сигналов12.
 

14.

 Для эффективного мультиплексирования Linux предоставляет системный вызов
 poll (), позволяющий процессу блокировать одновременно несколько файловых 
 дескрипторов. Постоянно проверяя каждый файловый дескриптор, процесс создает 
 отдельный системный вызов, определяющий, из каких файловых дескрипторов процесс будет
 читать, а на какие — записывать. Когда один или несколько таких файлов имеют 
 данные, доступные для чтения, или могут принимать данные, записываемые в них, poll ()
 завершается, и приложение может считывать и записывать данные в дескрипторах, не
 беспокоясь о блокировке. После обработки этих файлов процесс создает еще один 
 вызов poll (), блокируемый до готовности файла. Ниже показано определение poll ().
   int poll (struct pollfd * fds , int numfds , int timeout) ;
 Последние два параметра очень просты; numf ds задает количество элементов в 
 массиве, на который указывает первый параметр, a timeout определяет, насколько долго
 poll () должна ожидать события. Если в качестве таймаута задается 0, poll () никогда
 не входит в состояние таймаута.
  Первый параметр, f ds, описывает, какие файловые дескрипторы следует 
 контролировать, и для каких типов ввода-вывода. Это указатель на массив структур struct
 pollfd.
   struct pollfd 
   {
     int fd;
     short events;
     short revents;
   } ;
 Первый элемент, f d, является контролируемым файловым дескриптором, а элемент
 events описывает, какие типы событий подлежат мониторингу. Последний 
 представляет собой один или несколько перечисленных флагов, объединенных с помощью 
 логического "ИЛИ".
 
 POLL IN   Нормальные данные доступны для считывания из файлового дескриптора.
 
 POLLPRI   Приоритетные (внешние1) данные доступны для считывания.
 
 POLLOUT   Файловый дескриптор может принимать записываемые на него данные.
 
 

15.

  Системный вызов poll () был изначально представлен как часть Unix-дерева
 System V. Усилиями разработчиков BSD та же основная проблема была решена 
 похожим способом — предоставлением системного вызова seledt().
   int select(int numfds, fd_set * readfds, fd_set * writefds,
   fd__set * exceptfds, struct timeval * timeout) ;
 Три промежуточных параметра — readfds, writefds и exceptfds — определяют, за
 какими файловыми дескрипторами необходимо следить. Каждый параметр — это 
 указатель на f dset, структуру данных, позволяющую процессу определить произвольное
 количество файловых дескрипторов2. Ею манипулируют с помощью перечисленных
 ниже макросов.
 
 FD_ZER0 (fd_set * fds);         Очищает fds — в наборе не содержатся файловые 
                 дескрипторы. Этот макрос используется для инициализации структур fd_set.
 
 FD_SET(intfd, fd_set * fds);    Добавляет f d к f dset.
 
 FD CLR(intfd, fd set * fds) ;   Удаляет f d из f d_set.
 
 FD_ISSET (int fd, fd_set * fds); Возвращает true, если fd содержится в установленном fds.
 
 Первый набор файловых дескрипторов select (), readfds, содержит перечень
 файловых дескрипторов, вызывающих возврат вызова select (), когда они готовы для
 чтения или (для каналов и сокетов) когда процесс на другом конце файла закрыл его.
 Когда любой файловый дескриптор в writefds готов к записи, select () возвращается.
 exceptfds содержит файловые дескрипторы для слежения за исключительными 
 условиями. В Linux (так же, как и в Unix) это происходит только при поступлении внешних
 данных в сетевое подключение. В качестве любого из них можно указать NULL, если тот
 или иной тип события вас не интересует.
 
 Обладая одинаковой функциональностью, poll () и select () также имеют 
 существенные отличия. Наиболее очевидным отличием является таймаут, 
 поддерживающий миллисекундную точность для poll () и микросекундную точность для select ().
 В действительности же это отличие почти незначительно, поскольку ни один 
 системный вызов не будет подготовлен с точностью до микросекунды.
 Более важное отличие связано с производительностью. Интерфейс poll () обладает
 несколькими свойствами, делающими его намного эффективнее, чем select ().
 1. При использовании select () ядру необходимо проверить все файловые 
 дескрипторы между 0 и numf ds - 1, чтобы убедиться, заинтересовано ли приложение
 в событиях ввода-вывода для этого файлового дескриптора. Для приложений с
 большим количеством открытых файлов это может приэести к существенным 
 затратам, поскольку ядро проверяет, какие именно файловые дескрипторы 
 являются объектом интереса.
 2. Набор файловых дескрипторов передается ядру как битовая карта для select () и
 как список для poll (). Сложные битовые операции, необходимее для проверки
 и установки структур данных f d set, менее эффективны, чем простые проверки,
 требуемые для struct pollfd.
 3. Поскольку ядро переписывает структуры данных, передаваемые select (), 
 приложение вынуждено сбрасывать эти структуры каждый раз перед вызовом select ().
 С poll () результаты ядра ограничены элементом revents, что устраняет 
 потребность в восстановлении структур данных после каждого вызова.
 4. Использование структуры, основанной на множествах (например, fdset) не
 масштабируется по мере увеличения количества доступных процессу файловых
 дескрипторов. Поскольку ее размер статичен, а не динамичен (обратите внимание
 на отсутствие соответствующего макроса, например, FDFREE), она не может 
 расширяться или сжиматься в соответствии с потребностями приложения (или 
 возможностями ядра). В Linux максимальный файловый дескриптор, который можно
 установить в f d_set, равен 1023. Если понадобится больший файловый 
 дескриптор, select () работать не будет.
 
 

16.

 Следующая короткая программа, подсчитывающая количество системных вызовов в
 секунду, демонстрирует, насколько poll () эффективнее select ().
 
  int gotAlarm;
 
  void catch(int sig) 
  {
   gotAlarm = 1;
  }
 
  #define HIGH_FD 1000
 
  int main(int argc, const char ** argv) {
  int devZero;
  int count;
  fd_set select Fds;
  struct pollfd pollFds;
 
  devZero = open("/dev/zero", 0_RDONLY);
  dup2(devZero, HIGH_FD);
 
 /* с помощью signal выяснить, когда время истекло */
   signal(SIGALRM, catch);
   gotAlarm = 0;
   count =0;
   alarmA);
 
   while (!gotAlarm) 
   {
     FD_ZERO(&selectFds);
     FD_SET(HIGH_FD, &selectFds);
     select(HIGH_FD + 1, &selectFds, NULL, NULL, NULL);
     count++;
   }
 
   printf("Вызовов select() в секунду: %d\n", count);
   pollFds.fd = HIGH_FD;
   pollFds.events = POLLIN;
   count = 0;
   gotAlarm =0;
   alarm 1);
 
   while (!gotAlarm) 
   {
     poll(&pollFds, 0, 0);
     count++;
   }
 
   printf("Вызовов poll() в секунду: %d\n", count);
   
   return 0;
 }
 
 Здесь используется устройство /dev/zero, предоставляющее бесконечное 
 количество нулей, что обеспечивает немедленный возврат системных вызовов. Значение
 HIGHFD можно изменить, чтобы посмотреть, как деградирует select () по мере роста
 значений файловых дескрипторов.
 В определенной системе при не очень высоком значении HIGHFD, равном 2, 
 программа показала, что ядро за секунду может обрабатывать в четыре раза больше 
 вызовов poll (), чем вызовов select (). При увеличении HIGHFD до 1000 эффективность
 poll () становится в 40 раз выше, чем у select ().
 
 
 

17.

   Linux предлагает системные вызовы readv () и writev (), реализующие 
 разбросанное/сборное чтение и запись26. В отличие от стандартных элементов своего уровня,
 получающих по одному указателю и размеру буфера, эти системные вызовы получают
 массивы записей, каждая запись которых описывает буфер. Буферы читаются или 
 записываются в том порядке, в каком они приведены в массиве. Каждый буфер описывается
 с помощью структуры struct iovec.
   struct iovec 
   {
     void * iov_base; /* адрес буфера */
     size_t iov_len; /* длина буфера */
   } ;
 
 Ниже показаны прототипы readv () и writev ().
   int readv(int fd, const struct iovec * vector, size_t count);
   int writev(int fd, const struct iovec * vector, size_t count);
 
 Первый аргумент является файловым дескриптором, с которого можно считывать
 или на который можно записывать. Второй аргумент, vector, указывает на массив 
 элементов count struct iovec. Обе функции возвращают общее количество прочитанных
 или записанных байтов.
 Ниже приведена простая программа-пример, использующая writev() для 
 отображения простого сообщения на стандартном устройстве вывода.
 int main(void) 
 {
   struct iovec buffers[3];
   buffers[0].iov_base = "hello";
   buffers[0].iov_len = 5;
   buffers[1].iov_base = " " ;
   buffers[1].iov_len = 1;
   buffers[2].iov_base = "world\n";
   buffers[2].iov_len = 6;
   writev(l, buffers, 3);
   return 0;
 }
 

18.

  Функция getcwd () позволяет процессу найти имя своего текущего каталога 
 относительно корневого каталога системы.
   char * getcwd(char * buf, size_t size);
 
 Предусмотрено два системных вызова, меняющих текущий каталог процесса: chdir ()
 и f chdir ().
   int chdir(const char * pathname);
   int fchdir(int fd) ;
 Первый системный вызов получает имя каталога в качестве единственного 
 аргумента; второй принимает файловый дескриптор, являющийся открытым каталогом.
 
 Хотя в системе имеется один корневой каталог, значение / может меняться для 
 каждого процесса в системе. Это обычно делается для предотвращения доступа к 
 файловой системе со стороны сомнительных процессов (например, демоны ftp, 
 обрабатывающие запросы ненадежных пользователей). Например, если в качестве корневого каталога
 процесса определен /home/ftp, запуск chdir ("/") сделает текущий каталог процесса
 /home/ftp, a getcwd () вернет / для поддержания последовательности данного процесса.
 С целью обеспечения безопасности, если процесс пытается выполнить chdir ("/.."), он
 остается в своем каталоге / (каталог /home/ftp в масштабах всей системы), так же как
 и нормальные процессы, выполняющие chdir ("/..") остаются в корневом каталоге в
 масштабах всей системы. Процесс может легко изменять свой текущий корневой каталог
 с помощью системного вызова enroot (). Но путь нового корневого каталога 
 процесса интерпретируется с помощью текущего установленного корневого каталога, поэтому
 chroot (" / ") не модифицирует текущий корневой каталог процесса.
   int chroot(const char * path);
 
 Создание новых каталогов выполняется очень просто.
   int mkdir(const char * dirname, mode_t mode);
 
 Удаление каталога — это практически то же, что и удаление файла; меняется разве
 что имя системного вызова.
   int rmdir(char * pathname);
 Для успешного выпЬлнения rmdir () каталог должен быть пустым (он не 
 должен содержать ничего, кроме вездесущих . и . .); в противном случае возвращается
 ENOTEMPTY.
 
 Обычно программам требуется получать список файлов, содержащихся в каталоге.
 Linux предоставляет ряд функций, позволяющих обрабатывать каталог как абстрактный
 объект, что дает возможность избежать зависимости программ от точного формата 
 каталогов, реализуемого файловой системой. Открытие и закрытие каталогов 
 осуществляется очень просто.
   DIR * opendir(const char * pathname);
   int closedir(DIR * dir);
 Системный вызов readdir () возвращает имя следующего файла в каталоге. 
 Каталоги не упорядочены каким-либо образом, поэтому не стоит предполагать, что 
 оглавление каталога отсортировано. Если необходим упорядоченный список файлов, 
 сортировку придется выполнять самостоятельно. Функция readdir () определяется, как
 показано ниже.
   struct dirent * readdir(DIR * dir);
 
 Существуют две функции, которые облегчают приложениям просмотр всех файлов
 каталога, включая файлы в подкаталогах. Рекурсивный просмотр всех элементов 
 древовидной структуры (например, файловой системы) часто называется обходом (walk)
 дерева и он реализуется функциями f tw () и nf tw (). nf tw () представляет собой 
 усовершенствованную версию f tw.
   int ftw (const char *dir, ftwFunctionPointer callback, int depth);
 Функция ftw () начинает с каталога dir и вызывает указанную в callback 
 функцию для каждого файла в этом каталоге и его подкаталогах. Функция вызывается для
 всех типов файлов, включая символические ссылки и каталоги. 
 
 
 
 

19. Команда find выполняет в одном или нескольких деревьях каталогов поиск файлов, соответствующих определенным характеристикам. Ниже приведена простая реализация find, реализованная на основе nftw (). Она использует fnmatch () (см. главу 23) для реализации переключателя -name и иллюстрирует многие флаги, воспринимаемые nftw ().

 const char * name = NULL;
 int minDepth = 0, maxDepth = INT_MAX;
 
 int find (const char * file, const struct stat * sb, int flags,struct FTW * f) 
 {
   if (f->level < minDepth) return 0;
   if (f->level > maxDepth) return 0;
   if (name && fnmatch(name, file + f->base, FNM_PERIOD)) return 0;
   if (flags == FTW_DNR) {
   fprintf(stderr, "find: %s: недопустимые полномочия\п", file);
   } else {
   printf("%s\n", file);
   }
   return 0;
 }
 
 int main(int argc, const char ** argv) {
 int flags = FTWJPHYS;
 int i;
 int problem =0;
 int tmp;
 int re;
 char * chptr;
 /* поиск первого параметры командной строки (который должен находиться после списка путей */
 i = 1;
   while (i < argc && *argv[i] ! = '-') i ++;
 
   /* обработать опции командной строки */
   while (i < argc && !problem) 
   {
     if (!stremp(argv[i],"-name")) 
     {
       i++;
       if (i == argc)
       problem = 1;
       else
       name = argv[i++];
     } 
     else if (Istrcmp(argv[i], "-depth")) 
     {
       i++;
       flags |= FTWJDEPTH;
     }
      else if (Istrcmp (argv[i], "-mount") ||
       istrcmp (argv[i], "-xdev")) 
     {
       i++;
       flags |= FTW_MOUNT;
     } 
     else if (Istrcmp (argv[i], "-mindepth") ||
       Istrcmp (argv[i], "-maxdepth")) 
     {
       i++;
       if (i == argc)
       problem =1;
     else 
     {
       tmp = strtoul (argv[i++], &chptr, 10);
       if (*chptr)
       problem = 1;
       else if (Istrcmp(argv[i - 2], "-mindepth"))
       minDepth = tmp;
       else
       maxDepth = tmp;
     }
   }
 
 
 if (argc == 1 | | *argv[l] == '-') 
 {
   argv[l] = ".";
   argc = 2;
 }
 re = 0;
 i = 1;
 flags = 0;
 
 while (i < argc && *argv[i] != f-f)
   rc |= nftw(argv[i-f+], find, 100, flags);
 return re;
 }
 
 

20.

  Устройства, предназначенные для интерактивного использования1, обладают 
 сходным интерфейсом, который был выведен десятилетия назад для последовательных 
 терминалов TeleType и получил название tty. Интерфейс tty используется для доступа к 
 последовательным терминалам, консолям, терминалам xterm, сетевым регистрационным
 именам и тому подобному.
  Интерфейс tty прост концептуально и называется termios .
 Например, при сетевом подключении один конец устройства tty соединяется с 
 программой, предоставляющей сетевое подключение, а второй — с оболочкой, редактором
 или другой потенциально интерактивной программой. Если программы находятся на
 каждом конце, вы должны ясно понимать, на каком конце эмулируется оборудование;
 при сетевом подключении к сети подключается аппаратная сторона.
  Устройства tty с программным обеспечением на обоих концах называются 
 псевдотерминалами (pseudo-tty, или же просто pty). В первой части главы они рассматриваться
 не будут, поскольку программный конец pty обрабатывается так же, как и любое другое
 устройство tty. Позже мы поговорим о программировании аппаратного конца pty.
 
  Интерфейсы tty  работают в двух основных режимах: неформатируемый режим и 
 режим обработки. Неформатируемый режим передает данные в приложение без изменений.
 Режим обработки, известный также как канонический режим, поддерживает 
 ограниченный построчный редактор внутри драйвера устройства и пересылает отредактированные
 входные данные в приложение по одной строке за раз. Этот режим изначально 
 произрастает из универсальных систем, в которых специализированные устройства обработки
 входных данных предоставляли режим обработки без прерывания ЦП.
  Режим обработки обрабатывает определенные управляющие символы; например, по
 умолчанию AU уничтожает (стирает) текущую строку, AW стирает текущее слово, забой
 (АН) или Delete — предыдущий символ, a AR стирает и затем повторно набирает 
 текущую строку. Каждое из этих управляющих действий может быть повторно назначено
 другом символу. Например, на многих терминалах символу DEL (код 127) назначается
 действие забоя.
 
 Каждый сеанс (см. главу 10) рривязан к терминалу, с которого процессы сеанса 
 получают свои входные данные и в который пересылают свои выходные данные. Этот
 терминал может быть локальной консолью машины, терминалом, подключенным через
 последовательный канал, либо псевдотерминалом, устанавливающим соответствия во
 внешнем окне или по всей сети (подробнее о псевдотерминалах читайте в конце этой
 главы). Терминал, к которому относится сеанс, называется управляющим терминалом
 (или управляющим tty) сеанса. Терминал может быть управляющим терминалом только
 в одном сеансе за раз.
 
 Существуют две системные базы данных, используемые для отслеживания 
 зарегистрированных пользователей; utmp применяется для пользователей, зарегистрированных
 в данный момент, a wtmp является записью всех предыдущих регистрации со времени
 создания файла. Команда who использует базу данных utmp для отображения списка
 зарегистрированных пользователей, а команда last — базу данных wtmp для 
 отображения списка пользователей, зарегистрированных в системе после регенерации базы 
 данных wtmp. В системах Linux база данных utmp хранится в файле /var/run/utmp, а база
 данных wtmp — в файле /var/log/wtmp.
 
  
 

21. Все манипуляции tty осуществляются с помощью одной структуры, struct termios, а также нескольких функций, определенных в заголовочном файле . Из этих функций широко применяются только шесть. Когда не нужно устанавливать скорость передачи данных по линии, используются только две наиболее важных функции — tcgetattr() и tcsetattr().

   int tcgetattr(int fd,struct termios * tp) ;
   int tcsetattr(int fd, int oact, struct termios tp);
 Почти в каждом случае программы должны использовать tcgetattr () для 
 получения текущих установок устройства, модифицировать эти установки, а затем 
 применять tcsetattr () для активизации модифицированных установок. Многие программы
 также сохраняют копии оригинальных установок и восстанавливают их перед 
 завершением. В общем случае, следует модифицировать только интересующие вас установки;
 изменение других установок может усложнить работу пользователей с необычными 
 системными конфигурациями (или сбоями в вашем коде).
 
 Четыре флага termios контролируют четыре отдельных части управления вводом и
 выводом. Флаг входных данных, с if lag, определяет, каким образом 
 интерпретируются и обрабатываются принятые символы. Флаг выходных данных, с of lag, определяет,
 каким образом интерпретируются и обрабатываются символы, записываемые вашим
 процессом в tty. Управляющий флаг, с cf lag, определяет характеристики 
 последовательного протокола устройства и полезен лишь для физических устройств. Локальный
 флаг, elf lag, определяет, каким образом символы собираются и обрабатываются 
 перед отправкой на обработку выходных данных. На рис. 16.1 показана упрощенная схема
 того, какое место занимает каждый флаг в общей схеме обработки символов.
 
 Интерфейс termios определяет несколько функций. Все они объявлены в < termios.h>.
 Четыре из них являются обслуживающими функциями для переносимого 
 манипулирования структурой struct termios; остальные представляют собой системные вызовы.
 Функции, начинающиеся с cf, являются обслуживающими, а функции, начинающиеся
 etc — системными вызовами управления терминалом. Все системные вызовы управления
 терминалом генерируют SIGTT0U, если процесс в данный момент работает в фоне и 
 пытается манипулировать своим управляющим терминалом (см. главу 15).
 Кроме того, что уже было отмечено, эти функции возвращают 0 в случае успеха и -1
 при ошибке. Вызовы функций, которые можно использовать для управления 
 терминалом, описаны ниже.
   int tcgetattr(int fd, struct termios * t);
 Восстанавливает текущие настройки файлового дескриптора f d и помещает их в
 структуру, на которую указывает t.
   int tcsetattr(int fd, int options, struct termios * t);
 Устанавливает текущие настройки терминала для файлового дескриптора f d в 
 настройки, приведенные в t. Всегда используйте tcgetattr () для заполнения t, затем
 модифицируйте его. Никогда не заполняйте t вручную: некоторые системы требуют
 установки или снятия флагов, кроме флагов, определенных POSIX, поэтому 
 заполнение вручную является непереносимым.
 
 Существуют два запроса ioctl (.), которые, к сожалению, не были закодированы
 как часть интерфейса termios, хотя и должны были. 
 Для запроса текущего размера и установки нового размера применяйте структуру struct winsize.
 Для запроса текущего размера используйте следующий вызов:
   struct winsize ws;
   ioctl(fd, TIOCGWINSZ, &ws);
 Для установки нового размера заполните struct winsize и предумотрите такой вызов:
   ioctl(fd,- TIOCSWINSZ, &ws);
 
 Четыре флаговых переменных— c_iflag, с of lag, ccflag и c_lflag — 
 хранят флаги, управляющие определенными характеристиками. Заголовочный файл
 < termios .h> предоставляет символические константы битовых масок, которые, в свою
 очередь, предоставляют эти флаги. Устанавливайте их с помощью | = и 
 переустанавливайте с помощью &= и ~, как показано ниже.
 t.c_iflag |= BRKINT;
 t.c_iflag &= -IGNBRK;
 
 
 

22. Псведотерминалы, или pty — это механизм, позволяющий программе на уровне пользователя заменять место (логически говоря) драйвера tty для элемента оборудования, pty имеет два отдельных конца: конец, эмулирующий оборудование, называется ведущим устройством pty, а конец, обеспечивающий программы обычным интерфейсом tty, называется подчиненным компонентом pty. Подчиненный компонент выглядит как обычный tty; ведущее устройство выглядит как стандартное устройство символьного ввода-вывода и не является tty.

 В библиотеке libutil glibc предлагает две функции — openpty () и f orkpty (), —
 выполняющие почти всю работу по поддержке псевдотерминалов.
   int openpty (int * masterfd, int * slavefd, char * name,
       struct termios * term, struct winsize * winp);
   int forkpty (int * masterfd, char * name,
       struct termios * term, struct winsize * winp);
 
 Функция openpty () открывает ведущие и подчиненные псевдотерминалы, 
 необязательно используя структуры struct termios и struct winsize, передаваемые как
 опции настройки псевдотерминала, возвращая 0 в случае успеха и -1 в случае ошибки.
 Файловые дескрипторы ведущего устройства и подчиненного компонента 
 возвращаются аргументам masterfd и slavefd соответственно. Аргументы term и winp могут быть
 NULL, в случае чего они игнорируются, и настройка не выполняется.
 Функция f orkpty () работает так же, как и openpty (), но вместо возврата 
 файлового дескриптора подчиненного компонента она разветвляет псевдотерминал как
 управляющий терминал stdin, stdout и stderr для дочернего процесса, а затем, подобно
 fork (), возвращает идентификатор дочернего процесса родительскому и 0 дочернему
 либо -1 при возникновении ошибки.
 
 

23. Мы обсудим применение сокетов для двух протоколов, доступных через реализацию сокетов Linux. Наиболее важным протоколом, поддерживаемым системой Linux, является TCP/IP1 (Transmission Control Protocol/Internet Protocol — протокол управления передачей/протокол Internet), поскольку именно он управляет всем Internet. Мы также обратим внимание на сокеты домена Unix — механизм IPC, ограниченный одним компьютером. Хотя они и не работают через сеть, сокеты домена Unix широко применяются для приложений, работающих на одном компьютере.

 Подобно большинству остальных ресурсов Linux сокеты реализуются через 
 файловую абстракцию. Они создаются при помощи системного вызова socket (), который
 возвращает файловый дескриптор. После соответствующей инициализации сокета 
 данный дескриптор может использоваться для запросов read () и write (), как и любой
 другой файловый дескриптор. Когда процесс завершает работу с сокетом, его 
 необходимо закрыть через функцию close () для того, чтобы освободить все ресурсы, 
 ассоциированные с ним.
 
 Новые сокеты создаются системным вызовом socket (), который возвращает 
 файловый дескриптор для неинициализированного сокета. При создании сокет привязывает-,
 ся к определенному протоколу, однако соединение для сокета не устанавливается. На
 данном этапе еще невозможно считывать информацию из сокета и записывать в него.
   int socket (int domain, int type, i-nt protocol);
 Подобно open (), функция socket () возвращает значение меньше 0, если имела
 место ошибка, и файловый дескриптор, больший или равный нулю, если все прошло
 благополучно. Три параметра устанавливают протокол, который нужно использовать.
 
  И серверный, и клиентский процессы должны сообщить системе, какой адрес 
 использовать для сокета. Прикрепление адреса к локальной стороне сокета называется
 связыванием сокета и выполняется через системный вызов bind ().
   int bind(int sock, struct sockaddr * my_addr, socklen_t addrlen);
 
 После создания сокета сервер привязывает к нему адрес с помощью функции
 bind (). Далее процесс сообщает системе путем вызова функции listen (), что он 
 готов разрешить другим процессам соединение с данным сокетом (по указанному адресу).
 Если сокет привязан к адресу, ядро получает возможность обрабатывать попытки 
 соединения с данным адресом, поступающие от процессов. Однако соединение не 
 устанавливается немедленно. Слушающий процесс сначала должен согласиться с попыткой
 соединения через системный вызов accept (). До тех пор, пока новая попытка 
 соединения с определенным адресом не принята, она называется ожидающим соединением.
 Как правило, функция accept () блокируется до тех пор, пока к ней не пытается
 присоединиться некоторый клиентский процесс. Если сокет был помечен как неблоки-
 руемый через f cntl (), то функция accept () возвращает значение EAGAIN в том случае,
 если нет ни одного доступного клиентского процесса6. Системные вызовы select (),
 poll () и epoll могут использоваться для указания, ждать ли соединению обработки
 (эти вызовы помечают сокет как готовый для считыванияO.
 Ниже показаны прототипы listen () и accept ().
   int listen (int sock, int backlog);
   int accept(int sock, struct sockaddr * addr, sock len_t * addrlen);
 
 Как и серверы, клиенты могут сразу после создания сокета связывать с ним 
 локальный адрес. Обычно клиент пропускает этот шаг, предоставляя ядру присвоить сокету
 любой подходящий локальный адрес.
 После этапа связывания (который, впрочем, может быть пропущен) клиент 
 соединяется с сервером через системный вызов connect ().
   int connect(int sock, struct sockaddr * servaddr, socklen_t addrlen);
 
 После того как соединение установлено, приложение может найти адреса как 
 удаленного, так и локального концов сокета с помощью функций getpeername () и
 getsockname().
   int getpeername(int s, struct sockaddr * addr, socklen_t * addrlen);
   int getsockname(int s, struct sockaddr * addr, socklen_t * addrlen);
 
 Для преобразования порядка байтов хоста в сетевой порядок байтов используются
 четыре функции.
   unsigned int htonl(unsigned int hostlong);
   unsigned short htons(unsigned short hostshort);
   unsigned int ntohl(unsigned int netlong);
   unsigned short ntohs(unsigned short netshort);
 Несмотря на то что прототип каждой из этих функций принимает значение без 
 знака, все они отлично работают и для значений со знаком.
 Первые две функции htonl () и htons () преобразуют длинные и короткие 
 числа соответственно из порядка байтов хоста в сетевой порядок байтов. Последние две
 ntohl () и ntohs () выполняют обратные преобразования длинных и коротких чисел
 (из сетевого порядка в порядок хоста).
 
 
 

24.

  Соединения IPv4 представляют собой кортеж из 4-х элементов (локальный хост, 
 локальный порт, удаленный хост, удаленный порт). До установки соединения 
 необходимо определить каждую его часть. Элементы локальный хост и удаленный хост являются
 1Р\4-адресами. 1Ру4-адреса — это 32-битные D-байтовые) числа, уникальные для всей
 установленной сети. Как правило, они записываются в виде aaa.bbb.ccc.ddd, где каждый
 элемент адреса является десятичным представлением одного из байтов адреса машины.
 Первое слева число в адресе соответствует самому значимому байту в адресе. Такой 
 формат для 1Ру4-адресов известен как десятичное представление с разделителями-точками.
 
 В связи с тем, что большинство компьютеров вынуждено поддерживать работу 
 нескольких параллельных TCP/IP приложений, IP-номер не обеспечивает уникальную
 идентификацию для соединения на одной машине. Номера портов — это 16-битные
 числа, которые позволяют однозначно распознавать одну из сторон соединения на 
 данном хосте. Объединение IPv4-aapeca и номера порта обеспечивает идентификацию 
 стороны соединения где-либо в пределах одной сети TCP/IP (например, Internet является
 единой TCP/IP сетью). Две конечные точки соединения образуют полное ТСР-соеди-
 нение, таким образом, две пары, состоящие из IP-номера и номера порта, однозначно
 определяют TCP/IP соединение в сети.
 
 Распределение номеров портов для различных протоколов производится на основе
 раздела стандартов Internet, известного как официальные номера портов, который 
 утверждается Агентством по выделению имен и уникальных параметров протоколов Internet
 (Internet Assigned Numbers Authority, IANAI4. Общие протоколы Internet, такие как ftp,
 telnet и http, имеют свои номера портов. Большинство серверов предусматривают 
 данные службы на присвоенных номерах, что позволяет их легко найти. Некоторые сервера
 запускаются на альтернативных номерах портов, как правило, для поддержки 
 нескольких служб на одной машине15. Поскольку официальные номера портов не изменяются,
 система Linux просто находит соответствие между именами протоколов (обычно 
 называемых службами) и номерами портов с помощью файла /etc/services.
 
 Все номера портов попадают в диапазон от 0 до 65 535; в системе Linux они 
 разделяются на два класса. Зарезервированные порты с номерами от 0 до 1 024 могут 
 использоваться только процессами, работающими как root. Это позволяет клиентским
 программам иметь гарантию того, что программа, запущенная на сервере, не является
 троянским конем, активизированным каким-то пользователем.
 
 1Ру4-адреса хранятся в структуре struct sockaddrin, которая определяется 
 следующим образом.
   struct sockaddr_in 
   {
     short int sin_family; /* AF_INET */
     unsigned short int sin_port; /* номер порта */
     struct in_addr sin_addr; /* IP-адрес */
   }
 
 В IPv6 используется тот же самый кортеж (локальный хост, локальный порт, 
 удаленный хост, удаленный порт), что и в IPv4, и одни и те же номера портов A6-битные 
 значения).
 ПМ)-адреса локального и удаленного хостов являются 128-битными A6-байтовыми)
 числами вместо 32-битных чисел, которые использовались в IPv4. Применение таких
 больших адресов обеспечивает протоколы достаточным количеством адресов для 
 будущего развития (можно без проблем предоставить уникальный адрес каждому атому в
 Млечном Пути). На первый взгляд, это может показаться избыточной тратой ресурсов.
 Однако сетевые архитектуры имеют склонность небрежно относиться к адресам и 
 растрачивать огромное их число впустую, поэтому разработчики версии IPv6 предпочли
 перейти к 128-битным адресам сейчас, чем переживать о возможной необходимости 
 изменять адреса в будущем.
 
 В приложениях нередко требуется преобразовывать IP-адреса из удобочитаемых для
 человека представлений (либо десятичное с разделителями-точками, либо с 
 разделителями-двоеточиями) в двоичное представление struct inaddr и наоборот. Функция
 inetntop () принимает двоичный IP-адрес и возвращает указатель на строку, 
 содержащую десятичную форму с точками или двоеточиями.
   const char * inet_ntop(int family, const void address, char * dest, int size);
 
 Обратное преобразование строки, содержащей адрес с точками или двоеточиями, в
 двоичный IP-адрес выполняет функция inetpton ().
   int inet_pton(int family, const char * address, void * dest);
 
 Библиотечная функция getaddrinfo (J0 предлагает программам простой доступ к
 преобразованиям имен хостов DNS.
   int getaddrinfo(const char * hostname, const char * servicename,
           const struct addrinfo * hints, struct addrinfo ** res) ;
 Функция принимает имя хоста, имя службы (или оба из них) и превращает их в
 список IP-адресов. Затем с использованием hints список фильтруется и те адреса, 
 которые не нужны приложению, отбрасываются. Окончательный список возвращается в
 виде связного списка в переменной res.
 
 К счастью, переводить IP-адреса и номера портов в имена хостов и служб гораздо
 проще, чем наоборот.
   int getnameinfо ( struct sockaddr * addr, socklen_t addrlen,
   char * hostname, size_t hostlen,
   char * servicename, size_tservicelen,
   intflags);
 Здесь параметр addr указывает либо на struct sockaddr_in, либо на struct
 sockaddr_in6, член addrlen содержит размер структуры, на которую указывает addr.
 IP-адрес и номер порта, определенные addr, преобразуются в имя хоста, 
 сохраняющееся в ячейке, на которую указывает hostname, и в имя службы, сохраняющееся в
 servicename.
 
 
 

25.

  В системах Unix и Linux время отслеживается в секундах до или после начала эпохи,
 которое определяется как полночь 1 января 1970 года по UTC1. Положительные 
 значения времени относятся к периоду после начала эпохи; отрицательные — до начала эпо-
 хц. Для того чтобы обеспечить работу процессов в режиме текущего времени, в Linux,
 как и во всех остальных версиях Unix, предусмотрен системный вызов time ().
   time t time (time t *t);
 Функция time () возвращает количество секунд, прошедших с момента начала 
 эпохи. Если значение t не является нулевым, то данная функция передает в эту 
 переменную количество секунд, прошедших с начала эпохи.
 Для решения некоторых проблем требуется более высокая разрешающая 
 способность. В Linux предусмотрен еще один системный вызов — gettimeof day (), который
 предоставляет более подробную информацию.
   int gettimeofday(struct timeval *tv, struct timezone *tz) ;
 
 
 

26.

 
 Таймер — это простое средство для указаний определенной точки в будущем, в 
 которой должно произойти некоторое событие. Вместо того чтобы циклически запрашивать
 текущее время и проводить лишние растраты циклов центрального процессора, 
 программа может отправить в ядро запрос на получение уведомления о том, что прошло
 определенное количество времени.
  Существуют два способа применения таймеров: синхронный и асинхронный.
 Синхронное использование таймера возможно в единственном режиме — режиме 
 ожидания (дожидаться истечения времени таймера). Асинхронная работа таймера, как и
 любого другого асинхронного устройства, сопровождается сигналами. Сюрпризом 
 может оказаться то, что синхронный таймер может также вызывать сигналы.
 
 Процесс, сопровождающийся запросом на невыполнение в течение определенного
 количества времени, называется отложенным (или "спящим"). Для режима ожидания
 доступны четыре функции; каждая из них измеряет время в различных единицах. Они
 также ведут себя и взаимодействуют с остальными частями системы по-разному.
   unsigned int sleep;(unsigned int seconds) ;
 Функция sleep () вынуждает текущий процесс засыпать на время (в секундах), 
 указанное параметром seconds, или до тех пор, пока процесс не получит сигнал, 
 который он не может проигнорировать. 
 
   void usleep (unsigned long usee) ;
 Функция usleep () вынуждает текущий процесс засыпать на время (в 
 микросекундах), указанное параметром usee. Никакие сигналы не используются. 
 На большинстве платформ usleep () реализуется с помощью select ().
 
 Интервальные таймеры, будучи активизированными, непрерывно передают сигналы
 в процесс на систематической основе. Точное значение термина систематический 
 зависит от используемого интервального таймера. 
 Каждый из этих таймеров генерирует ассоциированный сигнал об истечении 
 таймера в пределах одного хода системных часов (как правило, 1-10 миллисекунд). 
 
 
 
 

27.

  В некоторых ситуациях все же требуется обеспечить невозможность 
 прогнозирования. Библиотека С содержит функции для генерирования ожидаемых 
 последовательностей псевдослучайных чисел. Эти функции легки в применении и являются 
 одинаковыми на всех платформах Unix. Рассмотрим пример типичного использования данных
 функций.
   srand(time(NULL) +getpid());
     do_something(rand ());
 }
 

28. Существуют две разновидности регулярных выражений: базовые регулярные выражения (basic regular expression — BRE) и расширенные регулярные выражения (extended regular expression — ERE). Они соответствуют (в первом приближении) командам дгер и едгер.

 Стандарт POSIX определяет четыре функции обработки регулярных выражений.
   int regcomp(regex_t *preg, const char * regex, int cflags);
 
   int regexec(const regex_t *preg, const char * string, ...
   
   void regfree(regex_t *preg);
 
   size_t regerror(int errcode, const regex_t *preg, ...
 
 Функция regexec () сравнивает строку с предварительно компилированным 
 регулярным выражением. 
 
 
 Прежде чем сравнивать строку с регулярным выражением, нужно выполнить ее
 компиляцию с помощью функции regcomp (). Аргумент regex_t *preg указывает на
 область хранения регулярного выражения. Чтобы каждое регулярное выражение было
 доступно одновременно, для него потребуется отдельный аргумент regext. Структура
 regext включает только один важный член, rensub, который определяет 
 количество подвыражений в регулярном выражении, заключенных в скобки. 
 
 Ниже представлен пример типичного вызова функции.
   if ((rerr = regcomp(&p, "(А(.*[А\\])#.*$) I Г Г#]+$)",  REG_EXTENDED|REG_NEWLINE))) 
   {
      if (rerr == REG_NOMATCH) 
     {
       /* строка просто не совпадает с регулярным выражением */
     } 
     else 
     { 
       /* какая-то другая ошибка, например, неправильно сформированное регулярное выражение */
     }
   }
 Данное расширенное регулярное выражение находит строки в файле, которые не
 включены в комментарии, или которые, по крайней мере, частично, заключены в 
 комментарии посредством символов # без префикса \. Эту разновидность регулярного 
 выражения удобно использовать в качестве простого анализатора синтаксиса для 
 конфигурационного файла какого-нибудь приложения.
 
 

29. С помощью библиотеки S-Lang, написанной Джоном Дэвисом (John E. Davis), можно осуществлять доступ к терминалам на среднем уровне. Все действия, связанные с управлением терминалами на низком уровне, осуществляются посредством набора подпрограмм, предлагающих прямой доступ к видеотерминалам и автоматически управляющих прокруткой и цветами. Несмотря на незначительную прямую подцержку окон и отсутствие в S-Lang каких-либо элементов управления, для таких задач эта библиотека предлагает удобную основу.

  
 Чтобы написать программу для посимвольного чтения из терминала и вывода 
 каждого символа в отдельной строке потребуется несложный код.
 int main(void) 
 {
   char ch = '\0';
 
   /*
   Начать обработку SLANG tty со следующими параметрами:
   -1 символ прерывания по умолчанию (обычно Ctrl-С)
   0 управление потоком не производится; все символы (кроме
   символа прерывания) будут переданы в программу 1 разрешение
   обработки выходных данных OPOST управляющих последовательностей
   */
   SLang_init_tty (-1, 0, l);
 
   while (ch != 'q') 
   {
     ch = SLang_getkey();
     printf ("чтение: %c 0x%x\n", isprint (ch) ? ch : ' ', ch) ;
   }
 
   SLang_reset_tty ();
   
   return 0;
 }
 
 Функции библиотеки S-Lang, предназначенные для вывода данных на терминал, 
 бывают двух разновидностей: функции управления терминалом (семейство SLtt) и 
 функции высокого уровня для управления экраном (семейство SLsmg).
 
 Прежде чем использовать функции библиотеки S-Lang для вывода данных на 
 терминал, программа должна послать S-Lang запрос на поиск текущего терминала (как это
 определено в переменной окружения TERM) в терминальной базе данных. Это 
 осуществляется следующим образом:
   void SLtt_get_terminfо(void);
 
 Инициализировать уровень управления экраном в S-Lang можно очень просто:
   void SLsmg_init_smg (void) ;
   SLsmg_init_smg()
 
 Прежде чем результаты выполнения последовательности подпрограмм SLsmg смогут
 быть отражены на физическом терминале, необходимо вызвать функцию SLsmgref resh ().
 
 Программы S-Lang могут перемещать курсор с помощью показанной ниже функции.
   extern void SLsmg_gotorc(int row, int column); ,
 
 Ниже приведен пример программы, которая сначала инициализирует возможности
 библиотеки S-Lang для управления экраном, а затем закрывает их. Хотя эта прбграмма
 выполняет лишь некоторые действия, она иллюстрирует основы использования 
 функциональных возможностей SLsmg библиотеки S-Lang.
 
 struct winsize ws;
 /* получение размеров терминала, подключенного к stdout */
 if (ioctl(l, TIOCGWINSZ, &ws)) 
 {
   реггог("сбой при получении размеров окна");
   return l;
 }
 SLtt_get_terminfo();
 SLtt_Screen_Rows = ws.ws_row;
 SLtt_Screen_Cols = ws.ws_col;
 SLsmg_init_smg();
 /* здесь находится ядро программы */
 SLsmg_gotorc(SLtt_Screen_Rows - 1, 0);
 SLsmg^refresh();
 SLsmg_reset_smg();
 SLang_reset_tty();
 return 0;
 

30.

  Процесс динамической загрузки заключается в открытии библиотеки, поиске 
 любого количества символов, обработке любых возникающих ошибок и закрытии 
 библиотеки. Все функции динамической загрузки объявляются в одном заголовочном файле,
 < dlfcn.h>, и определяются в libdl (чтобы воспользоваться функциями динамической
 загрузки скомпонуйте приложение с -ldl).
 Функция dlerror () возвращает строку, описывающую самую последнюю ошибку,
 которая возникла в одной из трех других функций динамической загрузки:
   const char * dlerror (void);
 Каждый раз при возврате значения она очищает состояние ошибки. Если не будет
 создано другое состояние ошибки, она продолжит выполнение, чтобы вернуть NULL
 вместо строки. Объяснение этого необычного поведения можно найти в описании
 функции dlsym().
 
 Функция dlopen () открывает библиотеку. Этот процесс включает поиск 
 библиотечного файла, открытие файла и выполнение некоторой предварительной обработки.
 Переменные окружения и параметры, переданные функции dlopen (), определяют 
 детали этого процесса.
   void * dlopen (const char * filename, int flag);
 
 Функция dlopen () возвращает дескриптор (handle) того разделяемого объекта, 
 который она открыла. Это непрозрачный объектный дескриптор, который следует 
 использовать только как аргумент для последующих вызовов функций dlsym () и dlclose ().
 Если разделяемый объект открывается несколько раз, функция dlopen () каждый раз
 будет возвращать один и тот же дескриптор, и с каждым новым вызовом счетчик 
 ссылок будет увеличиваться на единицу.
 Функция dlsym () производит поиск символа в библиотеке:
   void * dlsym (void * handle, char * symbol);
 handle должен представлять собой дескриптор, возвращенный функцией dlopen (),
 a symbol должен содержать строку с завершающим NULL, которая именует искомый
 символ. Функция dlsym () возвращает адрес определенного вами символа или NULL в
 неустранимой ошибки. 
 
 Функция dlclose () закрывает библиотеку.
   void * dlclose(void * handle);