пятница, 1 ноября 2013 г.

Драйверы и модули ядра Linux (обновление)

Мне где-то в форумных обсуждениях задали как-то вопрос:
Скажите пожалуйста, собираетесь ли Вы в ближайшее время выкладывать обновленную версию книги?
В Вашем блоге она уже давненько не обновлялась.
Я на это отвечал:
Пока не собирался. По очень простой причине:
- все вопросы для себя, которые у меня вызывали неясности - я разрешил (для себя)...
- всё, что я хотел сказать - я сказал
- сейчас у меня нет конкретной работы в этой области, которая давала бы материал для дальнейшего разбирательства...
- если бы у кого-то из читателей возникали конкретные вопросы (как сделать так чтобы...?) - то мы бы разобрали и нашли решение; таким образом и появились многие главы в теперешнем виде рукописи...
Но оказалось, что к материалу этому пришлось вернуться, главным образом из-за того, что начиная с версии ядра 2.6.34 и далее - существенно изменилась обработка операции ioctl() в драйвере. Кроме того, сделаны мелкие дополнения и изменения в тексте.

Последняя редакция обозначена как редакция 173 (380 стр.).

Скачать новые текст и архив примеров к нему можно:


- Google:
https://drive.google.com/file/d/0B__cqm ... sp=sharing - архив
https://drive.google.com/file/d/0B__cqm ... sp=sharing - текст


понедельник, 1 июля 2013 г.

Python: примеры и тесты, часть 5 - процессы

Параллельные процессы.

В составе модулей Python декларируется весьма много средств для запуска нового процесса из кода, многие из которых - альтернатива друг другу:
  • отдельный модуль subprocess, реализующий класс Popen;
  • отдельный модуль popen2, реализующий класс Popen3;
  • функции из модуля os группы system(), popen(), popen2(), popen3(), popen4() - выполняющие командную строку в новом экземпляре командного интерпретатора;
  • многочисленные функции из модуля os групп exec*() и spawn*() - подменяющие текущий процесс на указанный;
  • клонирование процесса вызовом fork() из модуля os; 
Наверняка существует ещё множество альтернативных реализаций, представленных сторонними производителями, что обуславливается лёгкостью тиражирования программного обеспечения для Python.

Все доступные средства запуска новых процессов отчётливо разделяются на три категории: а). запуск экземпляра командного интерпретатора (bash), который запустит процесс (system(), popen(), ...), б). подмена кода  текущего адресного процесса на код нового процесса (exec*() и spawn*()), в). создание копии текущего адресного пространства выполняющегося процесса (fork()). Первые две возможности оперируют с двоичным исполнимым форматом в файле, и полностью перекладывают выполнение своей деятельности на функциональность операционной системы. Меня для тестирования больше всего интересовала именно последняя возможность - создание клона процесса вызовом fork() (о причине почему "больше всего" чуть позже). Вот пример подобной программы (файл fork.py):

#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import time
import sys
import getopt
from rdtsc import rdtsc
 
delay = 1
procnum = 2
debuglevel = 0
 
opts, args = getopt.getopt( sys.argv[1:], "p:d:v" )
for opt, arg in opts:  # опции (ключи) командной строки
    if 0 == cmp( opt[ 1: ], 'p' ): procnum = int( arg )
    if 0 == cmp( opt[ 1: ], 'd' ): delay = int( arg )
    if 0 == cmp( opt[ 1: ], 'v' ): debuglevel = debuglevel + 1
 
childs = []
if debuglevel : print "родительский процесс %i" % os.getpid()
for i in range( 0, procnum ) :
    tim = rdtsc();
    try :
        pid = os.fork();
    except :
        print "error: create child process"
        sys.exit( 33 )
    if pid == 0 :      # в коде дочернего процесса
        trun = rdtsc() - tim;
        if debuglevel :
            print "дочерний процесс %i - циклов процессора на запуск: %u" % \
                  ( os.getpid(), trun );
        time.sleep( delay )
        trun = rdtsc() - tim;
        if debuglevel :
            print "дочерний процесс %i - время завершения: %u" % \
                  ( os.getpid(), trun );
        sys.exit( 3 )
    if pid > 0 :
      # в коде родительского процесса
        childs.append( pid )
        if debuglevel :

            print "%i: создан новый дочерний процесс %i" % ( os.getpid(), pid )
 
print "ожидание завершения дочерних процессов ..."
for p in childs :
    pid, status = os.wait()
    if debuglevel :

        print "код завершения процесса %i = %i" % ( pid, os.WEXITSTATUS( status ) )
print "все порождённые процессы успешно завершены"


Вот как выполняется этот код с детализированным уровнем отладочного вывода:

$ ./fork.py -p3 -d2 -v
родительский процесс 17743
17743: создан новый дочерний процесс 17744
17743: создан новый дочерний процесс 17745
17743: создан новый дочерний процесс 17746
ожидание завершения дочерних процессов ...
дочерний процесс 17746 - циклов процессора на запуск: 1136190
дочерний процесс 17745 - циклов процессора на запуск: 2535930
дочерний процесс 17744 - циклов процессора на запуск: 4064020
дочерний процесс 17746 - время завершения: 3330095370
дочерний процесс 17745 - время завершения: 3334456990
дочерний процесс 17744 - время завершения: 3342243510
код завершения процесса 17746 = 3
код завершения процесса 17745 = 3
код завершения процесса 17744 = 3
все порождённые процессы успешно завершены


Здесь много чего интересного: и последовательность старта выполнения созданных процессов, и порядок их завершения работы, и то, что интервал времени до старта создаваемого процесса имеет в точности тот порядок величины времени, что и при запуске потока, а это значит, что основные затраты производительности ложатся не на системные API создания параллельных ветвей выполнения, а на накладные расходы исполняющей системы Python...

А вот его выполнение для очень большого числа порождённых процессов:

$ ./fork.py -p100
ожидание завершения дочерних процессов ...
все порождённые процессы успешно завершены
$ ./fork.py -p500
ожидание завершения дочерних процессов ...
все порождённые процессы успешно завершены
$ ./fork.py -p721
ожидание завершения дочерних процессов ...
все порождённые процессы успешно завершены
$ echo $?
0
$ ./fork.py -p722
error: create child process
$ echo $?
33

Теперь возвратимся к сказанному ранее, что этот случай клонирования процесса самый любопытный... Почему?
  • во-первых, потому, что fork() - это самое сердце серверных технологий UNIX на протяжении десятилетий...
  • во-вторых, потому, что интересен вопрос: в интерпретирующей (исполняющей) системе Python что клонируется, какой процесс?
Смотрим (одновременно в двух терминалах):

$ ./fork.py -p5 -d20
ожидание завершения дочерних процессов ...
все порождённые процессы успешно завершены
$ ps -A | grep 'fork.py'
6882 pts/1 00:00:00 fork.py
6883 pts/1 00:00:00 fork.py
6884 pts/1 00:00:00 fork.py
6885 pts/1 00:00:00 fork.py
6886 pts/1 00:00:00 fork.py
6887 pts/1 00:00:00 fork.py

Как и следовало ожидать, клонируются экзепляры виртуальной машины Python (исполняющей байт-код системы), внутри которых уже, в свою очередь, выполняются копии приложения fork.py.

Ну и, в завершение, другой, более элементарный пример того, как из кода Python запускается новый экземпляр командного интерпретатотора (bash), выполняющий, в свою очередь, заказанное ему приложение:

  • родительский процесс (файл parent.py), передающий дочерним процессам работу по поиску вхождений в файлы текстовых фрагментов, заданных переменной word:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import subprocess
import sys
 
child = os.path.join( os.path.dirname(__file__), "./child.py" )
word = 'word'
file = [ './parent.py', './child.py' ]
pipes = []
 
for i in range( 0, 2 ):
    command = [ sys.executable, child ]
    pipe = subprocess.Popen( command, stdin=subprocess.PIPE )
    pipes.append( pipe )
    pipe.stdin.write( word.encode( "utf8" ) + b"\n" )
    pipe.stdin.write( file[ i ].encode( "utf8" ) + b"\n" )
    pipe.stdin.close()
 
while pipes:
    pipe = pipes.pop()
    pipe.wait()
  • порождённый процесс (файл child.py), причём, и то что искать (сигнатуру) и то где искать (имя файла для поиска) этот процесс получает из входного потока от своего родительского процесса:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
 
word = sys.stdin.readline().rstrip()
filename = sys.stdin.readline().rstrip()
try:
    with open( filename, "rb" ) as fh:
    while True:
        current = fh.readline()
    if not current:
        break
    if ( word in current ):
        print( "find: {0} {1}".format( filename, word ) )
except :
    pass


И вот что, в итоге, мы увидим:

$ ./parent.py
find: ./parent.py word
find: ./parent.py word
find: ./child.py word
find: ./child.py word
find: ./child.py word



Python: примеры и тесты, часть 4 - потоки

Опции командной строки.

Это немаловажная деталь для построения консольных приложений в стиле UNIX: обработка опций в командной строке. Для этого предоставляется модуль getopt, который позволяет обрабатывать опции на манер того, как делает одноимённый вызов C. Обрабатываются как короткие опции (односимвольные с одним предшествующим '-'), так и длинные (многосимвольные с двумя предшествующими '--'). Можно задавать опции как с требуемым значением, так и без. В общем: все удовольствия.

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

Потоки и синхронизация.

В описаниях Python везде упоминаются модуль реализации потоков низкого уровня - thread, и модуль реализации потоков высокого уровня - threading, надстроенный над модулем thread. Почти наверняка, доступно ещё много альтернативных реализаций от сторонних разработчиков, что обуславливается лёгкостью разработки тиражирования программного кода Python. Но все они, в конечном итоге, являются обёртками к pthread_t из POSIX API.

Реализация низкого уровня - модуль thread. Модуль крайне скудно описан в документации и литературе, так что пример кода использования я нигде не встретил, и его пришлось писать "с нуля". В модуле thread очень бедные возможности синхронизации - один единственный примитив (LockType = class lock), нечто на манер самого простейшего бинарного мютекса, ограничивающего критическую секцию кода. Для того, чтобы дождаться окончания дочерних потоков (реализовать "барьер", один из наиболее частых случаев) нужно строить искусственную конструкцию, по типу счётного семафора (файл tlspeed.py):

#!/usr/bin/python -O
# -*- coding: utf-8 -*-
from rdtsc import rdtsc
from calibr import calibr, delay_in_cycle
import getopt
import sys
import thread
import time
import string
 
debuglevel = 0
threadnum = 2                     # заказанное число порождаемых потоков
delay = 1
active = 0
numt = 0                          # текущее число активных дочерних потоков
lock = thread.allocate_lock()     # блокировка доступа к числу активных дочерних потоков
wait = thread.allocate_lock()     # блокировка ожидания завершения всех дочерних потоков
barier = { 'numt' : numt, 'lock' : lock, 'wait' : wait }
 
def thrfun( delay, num, tstart ): # функция потока
    st = rdtsc() - tstart
    barier[ 'numt' ] = barier[ 'numt' ] + 1
    barier[ 'lock' ].release()
    ss = "\t%i : %i <= старт: %u" % ( num, id, st )
    if not active : time.sleep( delay )
    else : delay_in_cycle( delay )
    barier[ 'lock' ].acquire()
    barier[ 'numt' ] = barier[ 'numt' ] - 1
    st = rdtsc() - tstart
    print "%s - финиш: %u" % ( ss, st )
    if 0 == barier[ 'numt' ] :
        barier[ 'wait' ].release()
    barier[ 'lock' ].release()
    return
 
opts, args = getopt.getopt( sys.argv[1:], "vt:d:a" )
for opt, arg in opts:             # опции (ключи) командной строки (-v, -t, -d, -a)
    if 0 == cmp( opt[ 1: ], 'v' ): debuglevel = debuglevel + 1
    if 0 == cmp( opt[ 1: ], 't' ): threadnum = string.atoi( arg )
    if 0 == cmp( opt[ 1: ], 'd' ): delay = string.atoi( arg )
    if 0 == cmp( opt[ 1: ], 'a' ): active = 1
if debuglevel > 0 :
    print opts
    print args
    print debuglevel
    print threadnum
 
barier[ 'wait' ].acquire()        # захват блокировки завершения
for n in range( threadnum ):      # запуск threadnum потоков
    barier[ 'lock' ].acquire()
    id = thread.start_new_thread( thrfun, ( delay, n, rdtsc() ) )
    print "\t%i : %i =>" % ( n, id )
barier[ 'wait' ].acquire()        # ожидание завершения всех потоков
print "завершены все %i потоков, \

       завершается ожидавший главный поток" % threadnum

Вот как выглядит исполнение этого примера (3 дочерних потока, каждый из которых выполняется по 3 секунды):

$ ./tlspeed.py -t3 -d3
0 : -1219589312 =>
1 : -1229980864 =>
2 : -1240466624 =>
0 : -1219589312 <= старт: 316450 - финиш: 4993234920
2 : -1240466624 <= старт: 235850 - финиш: 4992812200
1 : -1229980864 <= старт: 162580 - финиш: 4993124550
завершены все 3 потоков, завершается ожидавший главный поток


Здесь довольно странные (для меня) отрицательный значения в качестве идентификаторов отдельных потоков, хотя в документации сказано, что это просто числовое значение ... и, наверное, такие значение тоже имеют право быть.

Альтернативный вариант, это то, что декларируется как реализация высокого уровня - модуль threading. Здесь всё гораздо проще (но в кое-чём и сложнее ... например в передаче параметров в потоковую функцию уже после создания объекта класса Thread, непосредственно перед запуском на выполнение). Но в этом модуле уже представлено достаточно много самых разных примитивов синхронизации:  Lock, RLock, Condition, Semaphore, Event, Queue - это сильно облегчает описание взаимодействий потоков. Пример, эквивалентный предыдущему, может выглядеть как-то так:

#!/usr/bin/python -O
# -*- coding: utf-8 -*-
from rdtsc import rdtsc
from calibr import calibr, delay_in_cycle
import getopt
import sys
import threading
import time
import string
 
debuglevel = 0
threadnum = 2
delay = 1
active = 0
 
def thrfun( *args ):     
   # функция потока
    st = rdtsc() - args[ 2 ] # время старта потока
    ss = "\t%i : %s <= старт: %u" % \
         ( args[ 1 ], threading.currentThread().getName(), st )
    if not active : time.sleep( args[ 0 ] )
    else : delay_in_cycle( args[ 0 ] )
    st = rdtsc() - args[ 2 ] # время завершения потока
    print "%s - финиш: %u" % ( ss, st )
    return
 
opts, args = getopt.getopt( sys.argv[1:], "vt:d:a" )
for opt, arg in opts:        # опции (ключи) командной строки (-v, -t, -d, -a)
    if 0 == cmp( opt[ 1: ], 'v' ): debuglevel = debuglevel + 1
    if 0 == cmp( opt[ 1: ], 't' ): threadnum = string.atoi( arg )
    if 0 == cmp( opt[ 1: ], 'd' ): delay = string.atoi( arg )
    if 0 == cmp( opt[ 1: ], 'a' ): active = 1
if debuglevel > 0 :
    print opts
    print args
    print debuglevel
    print threadnum
 
threads = []
for n in range( threadnum ): # создание и запуск потоков
    parm = [ delay, n, 0 ]
    t = threading.Thread( target=thrfun, args=parm )
    threads.append( t )
    t.setDaemon( 1 )
    print "\t%i : %s =>" % ( n, t.getName() )
    parm[ 2 ] = rdtsc()
    t.start()
for n in range( threadnum ): # ожидание завершения всех потоков
    threads[ n ].join()
print "завершены все %i потоков, \

       завершается ожидавший главный поток" % threadnum

Вот как выглядит выполнение такого варианта (4 потока, выполняющиеся по 2 секунды), теперь вместо численных идентификаторов потока для их различения возникают имена потоков:

$ ./thspeed.py -t4 -d2
0 : Thread-1 =>
1 : Thread-2 =>
2 : Thread-3 =>
3 : Thread-4 =>
0 : Thread-1 <= старт: 558390 - финиш: 3329127080
2 : Thread-3 <= старт: 247300 - финиш: 3328592890
3 : Thread-4 <= старт: 211740 - финиш: 3328294390
1 : Thread-2 <= старт: 312410 - финиш: 3328573940
завершены все 4 потоков, завершается ожидавший главный поток

Обратите внимание на порядок старта потоков (временные метки) и на порядок их завершения. И в примере с низкоуровневым модулем thread, и здесь, в примере с высокоуровневым модулем threading, код допускает в качестве паузы выполнения потока использовать не пассивное ожидание (time.sleep()), а активное ожидание выполнением пустых циклов. При этом картина достаточно радикально меняется, что всегда возникает в параллелизмах (но это я оставлю для анализа любознательному уму заинтересованных читателей):

$ ./thspeed.py -t4 -d2 -a
0 : Thread-1 =>
1 : Thread-2 =>
2 : Thread-3 =>
3 : Thread-4 =>
1 : Thread-2 <= старт: 12039350 - финиш: 3339395210
0 : Thread-1 <= старт: 683100 - финиш: 3340604010
2 : Thread-3 <= старт: 21162120 - финиш: 3346219540
3 : Thread-4 <= старт: 41049890 - финиш: 3366116170
завершены все 4 потоков, завершается ожидавший главный поток


Функция активной задержки (delay_in_cycle()) реализована в собственном модуле calibr.py (он упоминался раньше) как:

def delay_in_cycle( delay = 1 ):
    t = time.time()
    while time.time() - t < delay :
        time.localtime()


Реализация потоков в Python вызывает ряд вопросов:

  • Является ли сама исполняющая система Python (виртуальная машина, исполняющая байт-код) многопоточной? То есть, распараллеливается ли многопоточное Python-приложение по числу доступных на оборудовании процессоров (ядер)? Мои наблюдения в системном мониторе показывают, что, похоже, это так и есть. 
  • Переключение между потоками Python-приложение должно происходить не по системному таймеру (как это происходит с потоками POSIX), а по полному завершению выполнения очередного байт-кода в потоке (об этом сказано и не в одном месте в документации). Более того, частота, с которой интерпретатор проверяет и переключает потоки, может быть переустановлена вызовом sys.setcheckinterval(). По умолчанию (утверждается) интерпретатор проверяет переключение потоков каждые 10 команд байт-кода (чем запросить эту величину из интерпретатора я не нашёл). Имеет ли эта модель какие-то последствия?
   

воскресенье, 30 июня 2013 г.

Python: примеры и тесты, часть 3 - SWIG

Показанная раньше техника создания модулей Python на C/C++ понятна и достаточно хорошо описана в документации в документации: передача объектов от исполняющей системы Python (параметры), преобразование значений переменных C/C++ в объекты Python (возврат значений)... Недостающие детали можно подсмотреть в заголовочном файле <python*/modsupport.h>. Но всё-таки такая ручная техника создания модулей на C/C++ достаточно громоздкая. Для её упрощения создано несколько инструментальных пакетов, автоматизирующих и упрощающих эту деятельность (Cython, SWIG и другие). Мной опробовано (пока?) только:

Использование SWIG.

В качестве упражнения я выбрал для разбирательства весьма простую задачу: подсчитать частоту вхождения в произвольный текст символов (256 символов из основной таблицы ASCII, только 1-байтовых, для упрощения). Реализующий C-код имеет вид (файл freq.c):

#include <stdlib.h>
 
int* frequency( char s[] ) {
    int *freq;
    char *ptr;
    freq = (int*)( calloc( 256, sizeof( int ) ) );
    if( freq != NULL )
        for( ptr = s; *ptr; ptr++ )
            freq[ *ptr ] += 1;
    return freq;
}


Теперь мы не пишем в коде программную реализацию интерфейса к этой  функции C, как в предыдущем рассмотрении, а составляем интерфейсный файл SWIG  (freq.i, расширение i):

%module freq
%typemap(out) int* {
    int i;
    $result = PyTuple_New( 256 );
    for( i = 0; i < 256; i++ )
        PyTuple_SetItem( $result, i, PyLong_FromLong( $1[ i ] ) );
    free( $1 );
}
extern int* frequency( char s[] );


Даже вот такая, несколько усложнённая форма описания, связана только с тем фактом, что С-функция frequency() динамически приобретает память, и после использования её нужно вернуть во избежание утечек памяти. Во многих же практических случаях (см. далее) интерфейсные описания и того проще. После создания мы обрабатываем файл описания вот таким образом:

$ swig -python freq.i

В результате чего генерируются файла исходного кода интерфейса модуля (freq_wrap.c). Из имеющихся файлов уже можно непосредственно собирать динамическую библиотеку, реализующую модуль Python:

$ gcc -c -fpic freq_wrap.c freq.c -DHAVE_CONFIG_H -I/usr/include/python2.7 -I/usr/lib/python2.7/co
$ ld -shared freq_wrap.o freq.o -o _
freq.so

Всё! Нам остаётся только написать тестирующее приложение Python (файл frtest.py) для непосредственного импорта созданного модуля:

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
from sys import argv
import freq
 
sarg = lambda : ( len( argv ) > 1 and argv[ 1 ] ) or str( input( "string?: " ) )
 
F = freq.frequency( sarg() )
s = ""
for i in range( 256 ):
    if F[ i ] != 0: s = s + ( "x%x:%d " % ( i, F[ i ] ) )
print s


В итоге мы получили работоспособный модуль Python (с именем freq), пусть не очень умный, но в достаточной мере иллюстрирующий технику использования SWIG:

$ python frtest.py "1234123121 abc ab a"
x20:3 x31:4 x32:3 x33:2 x34:1 x61:3 x62:2 x63:1


Прямой доступ к библиотекам C/C++.

В этой технике интересно создание модулей прямых интерфейсов к уже  существующим ранее целевым библиотекам (.so) C/C++ (при отсутствии их  аналогов в Python), это вообще практически без написания кода. В порядке иллюстрации сказанного я сделаю интерфейсный модуль к файловым операциям C. Файл интерфейсного описания SWIG (fileio.i) имеет вид:

%module fileio
extern FILE *fopen(char *, char *);
extern int fclose(FILE *);
extern unsigned fread(void *ptr, unsigned size, unsigned nobj, FILE *);
extern unsigned fwrite(void *ptr, unsigned size, unsigned nobj, FILE *);
extern void *malloc(int nbytes);
extern void free(void *);


Больше ничего нам, собственно, и не придётся делать:


$ swig -python fileio.i
$ gcc -c -fpic fileio_wrap.c -DHAVE_CONFIG_H -I/usr/include/python2.7 -I/usr/lib/python2.7/config
$ ld -shared fileio_wrap.o -lc -o _fileio.so


Тестовая задача (fiotest.py) на Python:

#!/usr/bin/python
# -*- coding: utf-8 -*-
from fileio import *
import fileio
from sys import argv
 
def filecopy( source, target ):  # Copy a file
    sum = long( 0 )
    f1 = fopen( source, "r" )
    if f1 == None: return -1
    f2 = fopen( target, "w" )
    buffer = malloc( 8192 )
    nbytes = fread( buffer, 1, 8192, f1 )
    while( nbytes > 0 ):
        fwrite( buffer, 1, nbytes, f2 )
        sum = sum + nbytes
        nbytes = fread( buffer, 1, 8192, f1 )
    free( buffer )
    fclose( f1 )
    fclose( f2 )
    return sum
 
n = filecopy( ( len( argv ) > 1 and argv[ 1 ] ) or "in.txt", \
              ( len( argv ) > 2 and argv[ 2 ] ) or "out.txt" )
print "скопировано %d байт" % n


И вот её функционирование:

$ python fiotest.py in.txt out.txt
скопировано 178 байт
$ ls -l *.txt
-rw-rw-r-- 1 olej olej 178 июня 21 23:48 in.txt
-rw-rw-r-- 1 olej olej 178 июня 22 00:14 out.txt




Python: примеры и тесты, часть 2 - модули C/C++

Написание модулей Python на языках C/C++.


Этот предмет в меру детально описан в оригинальной документации на сайте Python. Тем не менее, хотелось это проверить и проделать автономно этот путь (хотя бы для того, чтобы оценить его трудоёмкость).

Интерес представляла реализация измерителя временных интервалов наносекундного диапазона, построенного на чтении аппаратного счётчика процессорных циклов процессоров x86 (такой счётчик появился начиная с Pentium II, на других архитектурах Linux это не работает). Функция (файл rdtsc.c), читающая значение этого счётчика, на языке C (или, если точнее, на инлайн ассемблерной вставке, допускаемой как компилятором GCC, так и компилятором Clang):

unsigned long long rdtsc( void ) {
    unsigned long long int x;
    asm volatile ( "rdtsc" : "=A" (x) );
    return x;
}


Для более старых версий GCC, не распознающих мнемонику ассемблерной команды rdtsc из расширенного набора команд (для операционных систем Solaris, Minix 3), этот код может выглядеть так:

unsigned long long rdtsc( void ) {
    unsigned long long int x;
    asm volatile ( ".byte 0x0f, 0x31" : "=A" (x) );
    return x;
}


Хотелось бы получить интерфейс для вызова rdtsc() из среды Python. Этот пример крайне прост, чем хорош для экспериментирования, и обладает некоторой практической полезностью. Пишем, пользуясь документацией с сайта Python, интерфейсный модуль (файл rdtsc_wrap.c)

// это будет модуль Python "rdtsc":
#include <Python.h>
extern unsigned long long rdtsc( void );
  
PyObject* rdtsc_wrap( PyObject* self, PyObject* args ) {
    if( self != NULL ) return NULL; // обработка ошибки вызова
    return Py_BuildValue( "L", rdtsc() );

  
// таблица методов
static PyMethodDef rdtscmethods[] = {
    { "rdtsc", rdtsc_wrap, METH_NOARGS },
    { NULL, NULL }
};
  
// функция инициализации модуля
void initrdtsc() {
    Py_InitModule( "rdtsc", rdtscmethods );
}

Собираем всё это командами:

$ gcc -c -fpic rdtsc_wrap.c rdtsc.c -I/usr/include/python2.7
$ ld -shared rdtsc_wrap.o rdtsc.o -lc -o rdtscmodule.so


После чего получим реализующую модуль динамическую библиотеку:

$ ls -l *.so
-rwxrwxr-x 1 olej olej 2616 июня 26 23:23 rdtscmodule.so

Для проверки результата создадим два (для сравнения) тестовых приложения: одно на языке C и одно - на Pyton. Попутно реализуем ещё одну полезную функцию того же сорта (что и rdtsc()) - calibr(), интервал времени (в циклах частоты процессора) для вычисления двух последовательных, следующих друг за другом, вызовов rdtsc() (это можно считать временем выполнения самого вызова rdtsc() при измерении протяжённости очень коротких операций).

Тест на C (файл ctest.c):

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
extern unsigned long long rdtsc( void );
  
#define NUMB 10
static unsigned calibr( int rep ) {
    uint32_t n, m, sum = 0;
    n = m = ( rep <= 0 ? NUMB : rep );
    while( n-- ) {
        uint64_t cf, cs;
        cf = rdtsc();
        cs = rdtsc();
        sum += (uint32_t)( cs - cf );
    }
    return sum / m;

  
int main( int argc, char **argv, char **envp ) {
    printf( "число процессорных тактов = %llu\n", rdtsc() );
    printf( "калибровка последовательных вызовов:" );
    printf( " %lu(0)", calibr( 0 ) );
    int n;
    for( n = 10; n <= 100000; n*=10 )
        printf( " %lu(%d)", calibr( n ), n );
        printf( "\n");
    exit( EXIT_SUCCESS );
};

Выполнение такого теста:

$ ./ctest
число процессорных тактов = 79971042747980
калибровка последовательных вызовов: 117(0) 115(10) 115(100) 147(1000) 139(10000) 115(100000)


Подобный тест, но на этот раз написанный на Python:

  • файл модуля (calibr.py), реализующего calibr():
#!/usr/bin/python
# -*- coding: utf-8 -*-
from rdtsc import rdtsc
 
def calibr( args = 10 ):
    sum = 0L
    if int( args ) <= 0: n = 10
    else: n = int( args )
    m = n
    while n > 0 :
        cf = -( rdtsc() - rdtsc() )
        sum = sum + cf
        n = n - 1
    return sum / m

  • файл (ptest.py) вызывающего теста: 

#!/usr/bin/python -O
# -*- coding: utf-8 -*-
from rdtsc import rdtsc
from calibr import calibr
 
counter = []
for i in range( 5 ):
counter.append( rdtsc() )
print "счётчик процессорных циклов:\n", counter
 
arg = [ 0, 10, 100, 1000, 10000, 100000 ]
msg = "калибровка последовательных вызовов:"
for i in arg:
s = " %s(%i)" % ( str( calibr( i ) ), i )
msg = msg + s
print msg


Выполнение такого теста:

$ python -O ptest.py
счётчик процессорных циклов:
[79971137334370L, 79971137337650L, 79971137338900L, 79971137340040L, 79971137341090L]
калибровка последовательных вызовов: 548(0) 515(10) 947(100) 500(1000) 487(10000) 484(100000)


Всё достаточно предсказуемо!

Этот источник (rdtsc()) временных меток высокого разрешения я буду неоднократно использовать в тестах параллельного выполнение (потоков, процессов) для наблюдения за последовательностью операций.

Python: примеры и тесты, часть 1 - функции

На сегодня, по моему мнению, Python составляет лучший "тендем" разработчику C/C++ в Linux, из числа интерпретирующих языков. Ещё лет 5 назад я бы на должность такого кандидата называл бы Perl. Подобные инструменты крайне необходим в крупных проектах, начиная от написания скриптов и тестов, и до полновесной реализации проекта (в частности, в качестве подобных вспомогательных инструментов в разном качестве интересны, например, JavaScript и Lua).

Но многие стороны использования Python достаточно поверхностно  описаны в документации и публикациях, примеры кода их иллюстрирующие, временами, старые или неработающие ("писано с головы" ... не на клавиатуре проверялось). Поэтому по уточнению отдельных аспектов использования Python мне потребовалось написать (или воссоздать из существующих) примеры кода и тесты его применимости. И мне показалось, что некоторые из них могут быть любопытны и другим пользователям...

P.S. Меня совершенно не интересует использование Python в WEB-разработках, где может использоваться с равным успехом любой язык или технология: Perl, JavaScript, ... CGI-интерфейс, Ajax и другие всякие штучки. Это совершенно особая песня. Речь идёт только о использовании Python в "классическом" программировании.  

Хронологическое развитие темы Python развёрнуто в форумном обсуждении форумном обсуждении, но там оно, как и полагается такому обсуждению "в прямом эфире" скомкано и рвано... Кроме того, в том объёмном обсуждении затронуты другие сопутствующие темы: обсуждение публикаций и описаний языка, учебных курсов, интегрированных сред разработки (IDE), облегчающих разработку в Python. Здесь же всё это, как "рутинные" вопросы, будет отброшено, и  предполагается сделать некоторую упорядоченную подборку по предмету "примеры и тесты", как и сказано в заголовке. И дополнять их по мере накопления новых ... примеров и тестов...

Собственно, для программирования в UNIX/Linux меня интересовало всего несколько аспектов Python, по моему мнению явно недостаточно освещённых в литературе. А именно:

  • "ручное" написание  собственных модулей Python на языках C/C++;
  • использование для этого специализированных инструментальных средств, таких как Swig ... и других полезных инструментов;
  • обработка опций командной строки (модуль getopt) для построения консольных приложений в UNIX-стиле;
  • реализация параллельных потоков (причём как в технике "низкого уровня" - модуль thread, так и технике "высокого уровня" - модуль threading);
  • какие доступные механизмы синхронизаций потоков как в том, так и в другом варианте (поскольку как без синхронизации цена параллелизму - ноль);
  • использование fork() и других механизмов для порождения дочерних процессов;
  • работа с сигналами UNIX (модуль signal) ... 
  • средства метапрограммирования в Python: метаклассы, декораторы...
Ну и, возможно, некоторые другие...

Функциональное программирование. 

О функциональном стиле программирования в Python написано много: практически ни одно руководство по языку не обходится без главы с таким названием... Но всё написанное базируется на очень ограниченном числе публикаций от одного-двух авторов. Представляется так, что ... "слухи о функциональном программировании на Python сильно преувеличены". Функционально программировать лучше, пожалуй, на языках, предназначенных специально для функционального программирования: Lisp, Haskell, Scheme... Но в Python можно с успехом строить фрагменты в стиле функционального программирования, с пользой для итогового результата. Это достаточно сильно напоминает то, как функциональный стиль использовался в языке APL. И, конечно, как всегда в функциональном программировании, с широчайшим применением рекурсии, часто и для замены итерационных вычислений.

Традиционно самый распространённый приём иллюстрации рекурсии - это вычисление факториала (это как программа "Hello world!" в иллюстрации любого языка программирования). Но, не исключено, что факториал и есть обоснованно удачный пример в виду своей простоты. Вот такой пример:

#!/usr/bin/python
# -*- coding: utf-8 -*-
from sys import *
arg = lambda : ( len( argv ) > 1 and int( argv[ 1 ] ) ) or  \
                            int( input( "число?: " ) )
factorial = lambda x: ( ( x == 1 ) and 1 ) or x * factorial( x - 1 )
n = arg()
m = getrecursionlimit()
if n >= m :
    print "глубина рекурсии превышает установленную в системе %d, \ 
               переустановлено в %d" % \
              ( m, n + 1 )
setrecursionlimit( n + 1 )
print "n=%d => n!=%d" % ( n, factorial( n ) )
if getrecursionlimit() > m :
    print "глубина рекурсии восстановлена в %d" % m
setrecursionlimit( m )

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

$ ./fact2.py 10
n=10 => n!=3628800

$ ./fact2.py
число?: 10
n=10 => n!=3628800


Вся оставшаяся большая часть связана с тем, что я прочитал где-то в интернет-обсуждениях такую глупость: "... а вы попробуйте на Python вычислить факториал больше 1000 с соотвествующей глубиной рекурсии...". Попробуем:

$ ./fact2.py 10000
глубина рекурсии превышает установленную в системе 1000, переустановлено в 10001
n=10000 => n!=28462596809...000
глубина рекурсии восстановлена в 1000

Ну и показанноое число-результат (28462596809...000) будет занимать запись в 163 строки терминала. Но это огромное значение замечательно вычислилось исполняющей системой Python!

Хотя и без рекурсии реализация вычисления факториала в функциональном стиле выглядит красиво, используя встроенные функции высшего порядка, в данном случае reduce (это уже стиль языка APL):

factorial = lambda z: reduce( lambda x, y: x * y, range( 1, z + 1 ), 1 )
if len( sys.argv ) > 1: n = int( sys.argv[ 1 ] )
else: n = int( input( "число?: " ) )
print "n=%d => n!=%d" % ( n, factorial( n ) )


И снова проверяем:

$ ./fact3.py 5
n=5 => n!=120

$ ./fact3.py 50
n=50 => n!=30414093201713378043612608166064768844377641568960512000000000000



Функции высших порядков. 

Функциональное программирование предоставляет возможности, недостижимые в императивном программировании (или достигаемые очень искусственными приёмами). В частности, это связано с возможностью динамического создания новых функциональных объектов "на лету". Вот некоторые из таких приёмов:

  • Замыкание - когда из вызова возвращается объект-функция, конкретизированная параметрами образующего вызова; 
  • Функторы - параметризируемые (параметрами конструктора) объекты класса, который допускает функциональный вызов для экземпляров этого класса;
  • Частичное применение функции - когда функция нескольких переменных фиксируется для частных значений некоторых из этих переменных (уменьшается размерность переменных функции);
  • Карринг (англ. currying, или каррирование) — преобразование функции от многих аргументов в функцию, берущую свои аргументы по одному. 

Проверяем как это работает:

#!/usr/bin/python
# -*- coding: utf-8 -*-
def addClosure( val1 ):         # замыкание - closure
    def closure( val2 ):
        return val1 + val2
    return closure
cl = addClosure( 3 )            # новая функция
   
class addFunctor:               # эквивалентный предыдущему функтор 
    def __init__( self, val1 ):
        self.val1 = val1
    def __call__( self, val2 ):
        return self.val1 + val2
fn = addFunctor( 3 )            # экземпляр класса addFunctor
   
from functools import partial   # частичное применение функции
def addPart( a, b ):            # функция 2-х переменных
    return a + b
pr = partial( addPart, 3 )      # новая функция 1-й переменной
   
print cl( 2 ), fn( 2 ), pr( 2 ) # напечатает: 5 5 5

Каждый из 3-х вызовов в последнем операторе print синтаксически корректен, и возвратит своим значением 5.

Пример карринга (файл curry1.py):

#!/usr/bin/python
# -*- coding: utf-8 -*-
def spam( x, y ) :
    print u"x=%d, y=%d" % ( x, y )

   
spam1 = lambda x : lambda y : spam( x, y ) # новая функция
 
def spam2( x ) :
                          # новая функция
    def new_spam( y ) :
        return spam( x, y )
    return new_spam
 
spam1( 2 )( 3 )
spam2( 2 )( 3 )


$ ./curry1.py
x=2, y=3
x=2, y=3



понедельник, 25 марта 2013 г.

Разделяемые библиотеки

Начата и продолжается (по главам) публикация перевода большого описания: Ulrich Drepper, "Как писать разделяемые библиотеки". Это одна из показавшихся мне самых интересных публикаций за последние год-два, из всех, что попадались мне на глаза. Потому что это - прикладные аспекты профессиональной техники, которые крайне нужны любому работающему разработчику, но которые крайне скудно освещены в существующих в мире публикациях. И которую, к сожалению и как оказывается, достаточно слабо знают и превратно толкуют многие из всё тех же разработчиков (даже из кочующих по Интернет практических описаний использования разделяемых библиотек - некоторые описывают мифы и красивые народные легенды, совершенно не соответствующие действительности)...

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

среда, 20 марта 2013 г.

Книга "QNX/UNIX - анатомия параллелизма"

Небольшое дополнение в тему...

На сайте Либрусек я обнаружил страничку с некоторыми своими публикациями. Там лежит, в том числе, и PDF вариант этой, обсуждаемой ранее, рукописи "Модули ядра Linux" ... наверное, достаточно старая редакция, но, может, кому удобнее читать в PDF?

Но главное, из-за чего я помещаю это сообщение: там же лежит в формате DJVU моя книга "QNX/UNIX - анатомия параллелизма" , выходившая году в 2006-м в С.-Петербурге в издательстве "Символ-Плюс". Её материал сильно перекликается с тематикой ядра и модулей Linux (QNX в названии не должно смущать). Поэтому считаю целесообразным указать на место для бесплатного скачивания книги.




P.S. То, что подсказано комментариями (но комментарии не видны явно): архив (.tgz) к книге "QNX/UNIX - анатомия параллелизма" не лежит по URL, который указан в тексте книги (это уж недосмотр издателей). Мне об этом неоднократно писали, поэтому, вместо того, чтобы указывать правильный адрес на сайте издательства (который я не помню и его приходится каждый раз искать), я выложил этот архив в нескольких местах для свободного доступа. Одно из таких мест - вот здесь, в обсуждении, в качестве прикреплённого к сообщению файла.

среда, 30 января 2013 г.

Драйверы и модули ядра Linux (обновление)

Обновлены текст и архив примеров рукописи книги "Модули ядра Linux". Исправлены ошибки и существенно дополнены разделы относительно реализации стека сетевых протоколов. Последняя редакция обозначена как редакция 170.

Скачать новые текст и архив примеров к нему можно:

- на Google:
Это сам текст (в формате OpenOffice), и архив примеров к нему.