Архив рубрики: Без рубрики

perf : современный Linux профилировщик

Иногда возникает необходимость заглянуть внутрь программы, что-бы посмотреть почему она «так тормозит». Естественно начинается все с высокоуровневых средств htop/atop/nettop/iotop/sar. Но если они показывают, что производительность упирается в процессор, то необходимо знать какие функции требуют больше всего вычислительных мощностей. Это может помочь как переписать свою программу для провышения производительности, так и подобрать оптимальные настройки для чужой.

Собственно функция профилировщика — найти узкие места в программе и выдать нам как можно больше информации о том куда уходят процессорные такты. Можно выделить три основных модели профилирования:

Гарантированное профилирование (determine — не нашел как перевести это лучше). Целевая программа модифицируется (чаще всего перекомпилируется с определенными флагами — -pg для gcc). — в код внедряются вызовы библиотеки профилирования, собирающие информацию о выполняемой программе и передающие ее профилировщику. Наиболее часто замеряется время выполнения каждой функции и после тестового прогона программы можно увидеть вывод, подобный следующему:


% cumulative self self total
time seconds seconds calls ms/call ms/call name
33.34 0.02 0.02 7208 0.00 0.00 open
16.67 0.03 0.01 244 0.04 0.12 offtime
16.67 0.04 0.01 8 1.25 1.25 memccpy
16.67 0.05 0.01 7 1.43 1.43 write

Очевидным минусом гарантированного профилирования является необходимость перекомпиляции программы, что неудобно, а иногда и невозможно (если это не ваша программа, например). Также гарантированное профилирование значительно влияет на скорость исполнения программы, делая получение коректного времени исполнения затруднительным. Типичный представитель — gprof,

Статистическое профилирование (statistic profiling) — исполнение программы периодически (~100-1000 раз в секунду) останавливается, управление передается профилировщику, который анализирует текущий констекст исполнения. Затем, используя отладочную информацию или таблици символов из исполняемого файла программы и загруженных библиотек, определяется исполняемая в текущий момент функция и, если возможно, последовательность вызовов в стеке. Профилировщик увеличивает счетчик попаданий для активной функции, а после окончания профилирования можно увидеть сколько раз программа «была поймана» на исполнении определенных функций. Идея очень простая — если в некоторой функции A программа проводит в два раза больше времени, чем в функции B, то в среднем ( при достаточно длительном исполнении) мы будем в два раза чаще останавливаться внутри A, чем внутри B. Статистическое профилирование не требует перекомпиляции программы (хотя некоторые флаги компилятора могут помочь восстанавливать стек), значительно меньше гарантированного профилирования влияет на время и тайминги исполнения, но требует длительного или многократного прогона программы что бы получить надежные результаты. Типичный представитель — oprofile.

Профилирование на эмуляторе . Идея состоит в написании эмулятора, очень точно воспроизводящего целевой процессор, включая кеши со всеми механизмами замещения, декодировщики инструкций, конвееров и прочего. В общем случае задача похожа на не решаемую, учитываю сложность современных процессоров и огромное количество факторов, влияющих на производительность. Кроме этого профилирования на эмуляторе может занимать ооочень много времени из за накладных расходов на эмуляцию. Едиственный известный мне представитель/попытка — valgrind, хотя VTune от intel частично использует подобную технику для анализа кода.

По сумме характеристик статистическое профилирование является самым интересным (IMHO), посколько достаточно просто реализуется, практически не влияет на исполнение программы, способно профилировать даже те компоненты, для которых нет исходных текстов (например внешние библиотеки, линкуемые бинарно или с помощую .so файлов). Если же значительная нагрузка ложится на ядро или внешние сервисы (например комбинация из postgresql + python) то оно становится вообще единственным средством, помогающим понять «кто все съел».

Все серьезные современные процессоры имеют встроенную поддержку стат профилирования через аппара
тные счетчики событий производительности. Если кратко то в каждом ядре есть несколько регистров (для core 2 — 7), которые можно настроить на подсчет определенного типа событий, например тактов процессора, исполненных инструкций, промахов в кеши, TLB и т.п.

Обычно счетчики настраиваются на вызов прерывания при достижении определенного значения (например 1M тактов CPU), по входу в прерывание управление передается модулю профилировщика. В линукс до 2.6.31 поддержка аппаратных счетчиков была в основном в intel vtune и oprofile.

Большим плюсом счетчиков является возможность понять почему код работает с наблюдаемой скоростью, посколько они позволяют разложить время исполнения на более мелкие составляющие.

Начнем с динозавра стат профилирования — oprofile:

Без подсветки синтаксиса

## установим отладочные символы для вывода имен функций
# apt-get install oprofile python2.7-dbg python2.7-dev postgresql-9.1-dbg
# apt-get install libc6-dbg zlib1g-dbg libssl1.0.0-dbg

# запускаем модуль
# opcontrol --init

# PYTHON=/usr/bin/python2.7
# POSTGRES=/usr/lib/postgresql/9.1/bin/postgres

## настраиваем профайлер - не следить за ядром, используем
## такты процессора в качестве события профилирования, мониторить только
## питон, постгрес и библиотеки в их адресном пространстве

# opcontrol --no-vmlinux --event=CPU_CLK_UNHALTED:1000000
-i "$PYTHON,$POSTGRES" --separate=lib


## инициализируем базу - код теста приведен в конце статьи и лежит на git
# python test_psql.py psql fill 100000

## запускаем тест - выполнятеся 10M простейших 'select val from test_table where key=some_val'
# opcontrol --start; python test_psql.py psql read 10000000; opcontrol --stop

# opcontrol --dump
# opcontrol --shutdown
# opcontrol --deinit
# opreport -l | less

По итогу получаем следующий вывод:


CPU: Intel Architectural Perfmon, speed 2001 MHz (estimated)
Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (No unit mask) count 1000000
samples % image name app name symbol name
119760 2.0987 postgres postgres AllocSetAlloc
103579 1.8152 libc-2.13.so postgres strcoll_l
100119 1.7545 postgres postgres SearchCatCache
96138 1.6848 libcrypto.so.1.0.0 postgres sha1_block_data_order
93466 1.6379 libcrypto.so.1.0.0 python2.7 sha1_block_data_order
92630 1.6233 postgres postgres base_yyparse
89759 1.5730 libc-2.13.so postgres __memcpy_ssse3_back
71632 1.2553 libc-2.13.so postgres _int_malloc
71214 1.2480 libc-2.13.so python2.7 _int_malloc
70112 1.2287 libz.so.1.2.3.4 python2.7 longest_match
66124 1.1588 libcrypto.so.1.0.0 python2.7 _x86_64_AES_decrypt_compact
64521 1.1307 libcrypto.so.1.0.0 postgres _x86_64_AES_decrypt_compact
64130 1.1238 libc-2.13.so python2.7 __memcpy_ssse3_back
56956 0.9981 libc-2.13.so python2.7 malloc
52380 0.9179 libcrypto.so.1.0.0 postgres _x86_64_AES_encrypt_compact
52216 0.9151 libcrypto.so.1.0.0 postgres EVP_MD_CTX_cleanup
49158 0.8615 libcrypto.so.1.0.0 python2.7 _x86_64_AES_encrypt_compact
47041 0.8244 libz.so.1.2.3.4 python2.7 build_tree
46747 0.8192 libcrypto.so.1.0.0 python2.7 EVP_MD_CTX_cleanup
45194 0.7920 libz.so.1.2.3.4 postgres build_tree
42862 0.7511 libc-2.13.so python2.7 free

Итого: основное время системы проводит освобождая/выделяя память, шифруя передаваемую информацию и разбирая sql запросы.

Несмотря на то, что oprofile достаточно удобен его возможностей не хватало и длительное время Stephane Eranian разрабатывал perfmon3 — поддержку счетчиков проиводительности в ядре linux, которая однако никак не попадала в основное ядро…. А потом неожиданно появился Thomas Gleixner и вездесущий Ingo Molnar, которые предложили perf патч для ядра и одновременно закидали конфетами perfmon. Чуть позже Торвальдс закидал теми же конфетами разработчиков oprofile и одобрил включение утилит пользовательского режима для поддержки perf подсистемы в состав ядра.

В итоге патч был включен в Linux 2.6.31 и сейчас perf, IMHO, самая удобная утилита для профилирования бинарного кода в linux. perf входит в состав пакета linux-tools-common (ubuntu). Те, кто использует последние версии ядра из …. (к которым нет linux-tools-common) могут скачать исходники соответвующего ядра с kernel.org, перейти в папку tools/perf, сделать там make и скопировать полученный perf в /usr/bin/perf_USED_LINUX_KERNEL_VERSION

Без подсветки синтаксиса

$ cd tools/perf
$ make
$ sudo cp perf /usr/bin/perf_`uname -r`

По сравнению с oprofile модуль ядра perf получил:

  • более чистый API

  • больше аппаратных счетчиков

    (сравните «opcontrol —list-events» и «perf list | grep '[Hardware'»)

  • поддержку в структурах ядра (теперь при переключении процессов ядро сохраняет счетчики для старого процесса в его структуре и загружает в регистры процессора созраненные счетчики нового процесса — это улочшает результаты профилирования в многопоточной среде)

  • програмные счетчики — события из ядра: переключения контекстов, minor/major page fault, счетчики системных вызовов и интеграцию c systemtap.

Но конечных пользователей больше интересуют утилиты пользовательского режима, и тут много всего полезного ( perf невозможно использовать при загруженном модуле oprofile, так как они используют одни и те же апаратные регистры. Перед использованием perf необходимо выгрузить oprofile — «oprofile —deinit» ):

perf top — показывает в реальном времени какие функции/процессы в системе генерируют больше всего нагрузки. Например «perf top» покажеть распределение нагрузки по функциям. «perf top -e L1-dcache-loads» покажет кто хуже всех относится к кешу процессора «perf top -e dTLB-load-misses,iTLB-load-misses» покажет промахи в TLB и позволит оценить имеет ли смысл переключаться на огромные страници.

perf stat cmd генерирует отчет по отдельной команде — аналог утилиты time.

Без подсветки синтаксиса

$ perf stat ps
PID TTY TIME CMD
19784 pts/2 00:00:00 sudo
19785 pts/2 00:00:00 perf
19786 pts/2 00:00:00 ps

Performance counter stats for 'ps':

13.633273 task-clock # 0.962 CPUs utilized
9 context-switches # 0.001 M/sec
1 CPU-migrations # 0.000 M/sec
284 page-faults # 0.021 M/sec
21,595,618 cycles # 1.584 GHz [70.79%]
10,181,093 stalled-cycles-frontend # 47.14% frontend cyc les idle [70.90%]
6,232,603 stalled-cycles-backend # 28.86% backend cycles idle [80.06%]
31,611,007 instructions # 1.46 insns per cycle
# 0.32 stalled cycles per insn
6,798,332 branches # 498.657 M/sec
60,662 branch-misses # 0.89% of all branches [79.08%]

0.014176162 seconds time elapsed

perf record cmd исполняет программу и сохраняет итог профилирования в perf.dat, «perf report» позволит его просмотреть, а «perf annotate» покажет распределение событий по строкам исходного текста или по ассемблерному листингу.

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

В отличии от oprofile я не нашел как заставить perf записывать события только для некоторого набора бинарных файлов, но есть возможность поместить все необходимые процессы в отдельную группу(cgroup) и мониторить только ее.

Возможности perf на этом не исчерпываются, «perf help» покажет много полезных инструментов.

Пример использования perf с тем же тестом, что и oprofile:

Без подсветки синтаксиса

$ perf record -e cycles -c 1000000 python test_psql.py psql read 10000000
## просыпаемся раз в 1М тактов (~2к раз в секунду)
............
$ perf report


1.59% postgres postgres [.] AllocSetAlloc
1.46% postgres libc-2.13.so [.] __strcoll_l
1.35% postgres postgres [.] SearchCatCache
1.25% postgres libcrypto.so.1.0.0 [.] sha1_block_data_order
1.24% python libcrypto.so.1.0.0 [.] sha1_block_data_order
1.19% postgres libc-2.13.so [.] __memcpy_ssse3_back
1.13% postgres postgres [.] base_yyparse
0.94% python libz.so.1.2.3.4 [.] longest_match
0.94% postgres libc-2.13.so [.] _int_malloc
0.91% python libc-2.13.so [.] _int_malloc
0.87% python libcrypto.so.1.0.0 [.] _x86_64_AES_decrypt_compact
0.85% postgres libcrypto.so.1.0.0 [.] _x86_64_AES_decrypt_compact
0.82% python libc-2.13.so [.] __memcpy_ssse3_back
0.78% python libc-2.13.so [.] malloc
0.69% postgres libcrypto.so.1.0.0 [.] _x86_64_AES_encrypt_compact
0.67% postgres libcrypto.so.1.0.0 [.] EVP_MD_CTX_cleanup
0.67% python libcrypto.so.1.0.0 [.] _x86_64_AES_encrypt_compact
0.66% python libz.so.1.2.3.4 [.] build_tree
0.63% python libcrypto.so.1.0.0 [.] EVP_MD_CTX_cleanup
0.60% postgres libz.so.1.2.3.4 [.] build_tree
0.59% python libc-2.13.so [.] free
0.54% postgres postgres [.] hash_search_with_hash_value
0.53% postgres libz.so.1.2.3.4 [.] _tr_flush_block
0.52% python libz.so.1.2.3.4 [.] _tr_flush_block
0.50% python python2.7 [.] convertitem
.......

О накладных расходах: perf замедлял тест примерно на ~0.5%, а oprofile на 2-5%. Увеличивая порог срабатывания события можно уменьшить влияние профилировшика, но прийдется соответвенно удлиннить тесты для сохранения качества результатов.

В итоге perf дейсвительно Замечательный Инструмент, который позволяет не только профилировать свои программы но и практически незаметно мониторить что происходит в живой системе.

Исходники утилиты для тестов(требуют библиотеку futures):

Без подсветки синтаксиса

import sys
import time
import Queue
import futures
import psycopg2
import traceback
import multiprocessing


def exec_opers(test_class, start, stop, res_q, ops):
"""
Split test on chunks and put chunk size to result queue
when chunk execution finished
"""
try:
test_obj = test_class()

stops = range(start, stop, ops)[1:] + [stop]
curr_start = start

for curr_stop in stops:
test_obj.run(curr_start, curr_stop)
res_q.put(curr_stop - curr_start)

except Exception, x:
traceback.print_exc()
raise
finally:
# None mean thread finish work
res_q.put(None)


def do_tests(test_class, size, ops, th_count=None):
"run some amount of tests in separated threads"

if th_count is None:
th_count = multiprocessing.cpu_count()

res_q = Queue.Queue()

num_per_thread = size // th_count

with futures.ThreadPoolExecutor(max_workers=th_count) as executor:
vals = [(i * num_per_thread,
(i + 1) * num_per_thread)
for i in range(th_count)]

for start, stop in vals:
executor.submit(exec_opers, test_class, start, stop, res_q, ops)

left = th_count
done = 0
ppers = 0
pval = 0
ptime = time.time()
PERC_PER_STEP = 10

while left != 0:
try:
val = res_q.get(timeout=0.1)
if val is None:
left -= 1
else:
done += val
np = int(done * (100 / PERC_PER_STEP) / size)
if np != ppers:
ntime = time.time()
if ntime - ptime < 1E-3:
speed = "Too fast"
else:
speed = int((done - pval) / (ntime - ptime))

print "{0}% done. Performanc e - {1} ops/sec".format(
int(done * 100 / size), speed)
ptime = ntime
pval = done
ppers = np
except Queue.Empty:
pass


class PSQLBase(object):
def __init__(self):
self.conn, self.curr = self.conn_and_cursor()
self.curr.execute("select count(*) from test_table")
self.db_sz = int(self.curr.fetchone()[0])

def __del__(self):
self.curr.close()
self.conn.close()

@classmethod
def conn_and_cursor(cls):
conn = psycopg2.connect("host=localhost dbname=test user=test password=test")
return conn, conn.cursor()

@classmethod
def clear_data(cls):
conn, curr = cls.conn_and_cursor()
curr.execute("delete from test_table")
conn.commit()
curr.close()
conn.close()

@classmethod
def insert_data(cls, sz, step=1000):
conn, curr = cls.conn_and_cursor()
for i in range(sz / step):
vals = [(str(i * step + j), 'test_data') for j in range(step)]
curr.executemany("insert into test_table (key, val) values (%s, %s)", vals)
conn.commit()

curr.close()
conn.close()


class PSQLRead(PSQLBase):
def run(self, start, stop):
for i in xrange(start, stop):
self.curr.execute("select val from test_table where key=%s", (str(i % self.db_sz),))
self.curr.fetchall()


class PSQLWrite(PSQLBase):
def run(self, start, stop):
for i in xrange(start, stop):
self.curr.execute("insert into test_table (key, val) values (%s, %s)", (str(i), 'test data'))
self.conn.commit()


def main(argv=None):
if argv is None:
argv = sys.argv[1:]

tp = argv[0]

if tp == 'psql':
Base, R, W = PSQLBase, PSQLRead, PSQLWrite

argv = argv[1:]

if argv[0] != 'clear':
sz = int(argv[1])
if argv[0] == 'clear':
Base.clear_data()
elif argv[0] == 'fill':
Base.insert_data(sz)
elif argv[0] == 'read':
do_tests(R, sz, 1000)
elif argv[0] == 'write':
do_tests(W, sz, 50)
else:
print "Unknown cmd '%s'" % argv[0]
return 1
return 0

if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))

И скрипта для профилирования:

Без подсветки синтаксиса

#!/bin/bash
set -x

PROFILER=perf

PYTHON=/usr/bin/python2.7
POSTGRES=/usr/lib/postgresql/9.1/bin/postgres

OK=0
if [ "$PROFILER" == "oprofile" ] ; then
OK=1
opcontrol --init
opcontrol --reset
opcontrol --callgraph=10 --no-vmlinux --event=CPU_CLK_UNHALTED:1000000 -i "$PYTHON,$POSTGRES" --separate=lib
opcontrol --start
fi

if [ "$PROFILER" == "perf" ] ; then
OK=1
rm perf.data perf.data.old 2>&1 >/dev/null
fi

if [ "0" == "$OK" ] ; then
echo "Unknown profiler $PROFILER" >&2
exit 1
fi

if [ "$PROFILER" == "oprofile" ] ; then
$PYTHON test_psql.py $@
else
perf record -a -g $PYTHON test_psql.py $@
fi

if [ "$PROFILER" == "oprofile" ] ; then
opcontrol --dump
opcontrol --shutdown
opcontrol --deinit
fi

P.S. Приведенная утилита ни в коем случае не предназначается в текущем варианте для тестов PostgreSQL или чего либо еще. Ее единственная задача — показать работу профилировщика.

Исходники этого и других постов со скриптами лежат тут — my blog at githubПри использовании их, пожалуйста, ссылайтесь на koder-ua.blogspot.com

Автор: konstantin danilov

Запуск процессов в linux с ограничением ресурсов

Иногда хочется ограничить максимальное количество ресурсов доступных процессу. Последней пинком стали юнит-тесты из текущего проекта — из-за ошибок они несколько раз съели все 8Gb ОЗУ и отправили систему в глубокий своп, откуда она со скрипом доставалась минут 15. Полная виртуализация таких случаях избыточна — нужно что-то по легче. В linux для этого есть cgroups (control groups) — они позволяют поместить процесс (вместе со всеми его потомками) в контейнер, имеющий доступ только к части ресурсов системы. На самом деле cgroups умеют больше, чем просто ограничение ресурсов — тут и счетчики производительности и другая статистика.

cgroups можно манипулировать вручную или с помощью libcgroup. Последний способ значительно удобнее и по нему есть отличная документация от redhat. Она обязательна к прочтению — есть несколько не очевидных моментов (для пользователей ubuntu — в этом дистрибутиве по умолчанию cgroups монтируются в /sys/fs/cgroups).

Краткий пересказ документации на примере ubuntu/libcgroup.

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

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

Без подсветки синтаксиса

# apt-get install cgroup-bin

# apt-get install cgroup-bin

После установки в системе появится сервис cgconfig, который автоматически создает иерархию групп и контроллеров по описанию из файла /etc/cgconfig.cfg. После правки файла сервис необходимо перезапустить. Иногда он не может корректно перезапуститься из-за не отмонтированных при останове контроллеров. Лечится это ручным отмонтированием контроллеров:

Без подсветки синтаксиса

$ sudo service cgconfig restart
[sudo] password for koder:
stop: Job failed while stopping
$ lscgroups
cpuset:/
cpuset:/sysdefault
$ rm -rf /sys/fs/cgroup/cpuset/sysdefault
$ umount /sys/fs/cgroup/cpuset
$ umount /sys/fs/cgroup
# # теперь можно перезапускать

$ sudo service cgconfig restart
[sudo] password for koder:
stop: Job failed while stopping
$ lscgroups
cpuset:/
cpuset:/sysdefault
$ rm -rf /sys/fs/cgroup/cpuset/sysdefault
$ umount /sys/fs/cgroup/cpuset
$ umount /sys/fs/cgroup
# # теперь можно перезапускать

Второй интересный сервис — cgred. Он умеет автоматически распределять процессы по группам в зависимости от правил, описанных в /etc/cgrules.conf. По умолчанию не запущен.

После установки cgroup-bin в /sys/fs/cgroup появится группы из стартового конфига:

Без подсветки синтаксиса

$ ls /sys/fs/cgroup
cpu cpuacct device s freezer memory

$ ls /sys/fs/cgroup
cpu cpuacct devices freezer memory

К каждой иерархии привязан один контроллер с соответствующим именем. Что-бы поместаить процесс под управление одного из контроллеров нужно выполнить 3 шага:

  • Сделать группу. sudo mkdir /sys/fs/cgroup/memory/test создаст группу test под управлением контроллера memory. В папке /sys/fs/cgroup/memory/test тут же появятся специальные файлы для управления и мониторинга группы.
  • Настроить контроллер. Некоторые контроллеры готовы к работе сразу, а некотрые требуют предварительной настройки. Правила настройки контроллеров не очень очевидны и описаны в документации. Для memory минимально необходимо записать ограничение в файл limit_in_bytes.

Без подсветки синтаксиса

$ sudo echo 4M > /sys/fs/cgroup/memory/test/limit_in_bytes

$ sudo echo 4M > /sys/fs/cgroup/memory/test/limit_in_bytes

Теперь процессы в группе могут использовать не больше 4M ОЗУ на всех.

  • Перенести процесс в группу — для этого нужно записать его pid в файл /sys/fs/cgroup/memory/test/tasks.

Когда группа станет не нужна ее можно удалить через rm -rf. Что-бы создать подгруппу нужно сделать новую папку в папке группы.

cgroups-bin предлагает чуть более удобный интерфейс для управления группами:

Без подсветки синтаксиса

# cgcreate -g memory:/test # создать группу tt под управлением контроллера memory

# lscgroup memory:/
memory:///
memory:///test
memory:///sysdefault


# cgset -r memory.limit_in_bytes=4M memory:test # устанавливаем limit
# cgexec -g memory:test python -c "range(10000000000)" # пытаемся сделать всем плохо
Killed

# cgdelete memory:test # удалить группу

# lscgroup memory:/
memory:///
memory:///sysdefault

# cgcreate -g memory:/test # создать группу tt под управлением контроллера memory

# lscgroup memory:/
memory:///
memory:///test
memory:///sysdefault


# cgset -r memory.limit_in_bytes=4M memory:test # устанавливаем limit
# cgexec -g memory:test python -c "range(10000000000)" # пытаемся сделать всем плохо
Killed

# cgdelete memory:test # удалить группу

# lscgroup memory:/
memory:///
memory:///sysdefault

Чтобы не делать это каждый раз руками можно настроить cgred или написать автоматизирующий скрипт; я сделал себе такой:

Показать код

#!/bin/bash
#set -x
USAGE="Usage: `basename $0` [-h] [-m MEM_LIMIT_IN_MEGS] [-c CPUS] [-i IOLIMIT] [-u USER] CMD"

cgroup_mount=/sys/fs/cgroup

GROUP=app_cell_1
while [ true ] ; do

OK=1
for cgroup in `lscgroup | awk -F: '{print $1}' | uniq`; do
if [ -d $cgroup_mount/$cgr oup/$GROUP ] ; then
OK=0
break
fi
done

if (( $OK==1 )) ; then
break
fi

GROUP=app_cell_$RANDOM
done

MEMLIMIT=
CPUS=
USER=
IOLIMIT=

while getopts hm:c:i:u: OPT; do
case "$OPT" in
h)
echo $USAGE
exit 0
;;
c)
CPUS=$OPTARG
echo "CPUS = $CPUS"
;;
i)
IOLIMIT=$OPTARG
;;
m)
MEMLIMIT=$OPTARG
;;
u)
USER=$OPTARG
;;
?)
echo $USAGE >&2
exit 1
;;
esac
done

shift $(($OPTIND - 1))

if [ $# -eq 0 ]; then
echo $USAGE >&2
exit 1
fi

CMD=$@

#cgdelete memory:/$GROUP 2>/dev/null
#cgdelete cpuset:/$GROUP 2>/dev/null
#cgdelete blkio:/$GROUP 2>/dev/null

CGEXEC_OPT=
LIMITS=0

if [ -n "$MEMLIMIT" ] ; then
LIMITS=1
cgcreate -g memory:/$GROUP
cgset -r memory.limit_in_bytes=$MEMLIMIT $GROUP
cgset -r memory.memsw.limit_in_bytes=$MEMLIMIT $GROUP
CGEXEC_OPT="$CGEXEC_OPT -g memory:$GROUP"
fi

if [ -n "$CPUS" ] ; then
LIMITS=1
cgcreate -g cpuset:/$GROUP
cgset -r cpuset.cpus=$CPUS $GROUP
cgset -r cpuset.mems=0 $GROUP
CGEXEC_OPT="$CGEXEC_OPT -g cpuset:$GROUP"
fi

if [ -n "$IOLIMIT" ] ; then
echo "IO limits not supported yet" >&2
fi

if (( $LIMITS==0 )) ; then
echo "At least one limit should be set" >&2
echo $USAGE >&2
exit 1
fi

if [ -e "$USER" ] ; then
cgexec $CGEXEC_OPT $CMD
else
cgexec $CGEXEC_OPT su "$USER" -c "$CMD"
fi

#!/bin/bash
#set -x
USAGE="Usage: `basename $0` [-h] [-m MEM_LIMIT_IN_MEGS] [-c CPUS] [-i IOLIMIT] [-u USER] CMD"

cgroup_mount=/sys/fs/cgroup

GROUP=app_cell_1
while [ true ] ; do

OK=1
for cgroup in `lscgroup | awk -F: '{print $1}' | uniq`; do
if [ -d $cgroup_mount/$cgroup/$GROUP ] ; then
OK=0
break
fi
done

if (( $OK==1 )) ; then
break
fi

GROUP=app_cell_$RANDOM
done

MEMLIMIT=
CPUS=
USER=
IOLIMIT=

while getopts hm:c:i:u: OPT; do
case "$OPT" in
h)
echo $USAGE
exit 0
;;
c)
CPUS=$OPTARG
echo "CPUS = $CPUS"
;;
i)
IOLIMIT=$OPTARG
;;
m)
MEMLIMIT=$OPTARG
;;
u)
USER=$OPTARG
;;
?)
echo $USAGE >&2
exit 1
;;
esac
done

shift $(($OPTIND - 1))

if [ $# -eq 0 ]; then
echo $USAGE >&2
exit 1
fi

CMD=$@

#cgdelete memory:/$GROUP 2>/dev/null
#cgdelete cpuset:/$GROUP 2>/dev/null
#cgdelete blkio:/$GROUP 2>/dev/null

CGEXEC_OPT=
LIMITS=0

if [ -n "$MEMLIMIT" ] ; then
LIMITS=1
cgcreate -g memory:/$GROUP
cgset -r memory.limit_in_bytes=$MEMLIMIT $GROUP
cgset -r memory.memsw.limit_in_bytes=$MEMLIMIT $GROUP
CGEXEC_OPT="$CGEXEC_OPT -g memory:$GROUP"
fi

if [ -n "$CPUS" ] ; then
LIMITS=1
cgcreate -g cpuset:/$GROUP
cgset -r cpuset.cpus=$CPUS $GROUP
cgset -r cpuset.mems=0 $GROUP
CGEXEC_OPT="$CGEXEC_OPT -g cpuset:$GROUP"
fi

if [ -n "$IOLIMIT" ] ; then
echo "IO limits not supported yet" >&2
fi

if (( $LIMITS==0 )) ; then
echo "At least one limit should be set" >&2
echo $USAGE >&2
exit 1
fi

if [ -e "$USER" ] ; then
cgexec $CGEXEC_OPT $CMD
else
cgexec $CGEXEC_OPT su "$USER" -c "$CMD"
fi

Используется так:

Без подсветки синтаксиса

$ sudo ./incontainer -u koder -m 1G -c 0-1 nosetests
# #nosetests ограничен 1 гигабайтом ОЗУ и только двумя ядрами.

$ sudo ./incontainer -u koder -m 1G -c 0-1 nosetests
# #nosetests ограничен 1 гигабайтом ОЗУ и только двумя ядрами.

Скрипт каждый раз создает новую группу вида app_cell_случайное_число, которые нужно периодически удалять, когда в них не останется ни одного процесса. Например, так:

Показать код

#!/bin/bash
cgroup_mount=/sys/fs/cgroup

for cgroup in `lscgroup | awk -F: '{print $1}' | uniq` ; do
for group in `ls -1d $cgroup_mount/$cgroup/app_cell_* 2>/dev/null` ; do
#group=$cgroup_mount/$cgroup/$_group
if [ -d $group ] ; then
TC=`cat $group/tasks | wc -l`
if (( $TC==0 )) ; then
gname=$cgroup:/`basename $group`
echo "Group $gname is empty - clear it"
cgdelete -r $gname
fi
fi
done
done

#!/bin/bash
cgroup_mount=/sys/fs/cgroup

for cgroup in `lscgroup | awk -F: '{print $1}' | uniq` ; do
for group in `ls -1d $cgroup_mount/$cgroup/app_cell_* 2>/dev/null` ; do
#group=$cgroup_mount/$cgroup/$_group
if [ -d $group ] ; then
TC=`cat $group/tasks | wc -l`
if (( $TC==0 )) ; then
gname=$cgroup:/`basename $group`
echo "Group $gname is empty - clear it"
cgdelete -r $gname
fi
fi
done
done

P.S. Ковыряясь в cgroup наткнулся на очень интересный системный вызов

Без подсветки синтаксиса

prctl(PR_SET_SECCOMP, 0, 0, 0, 0)

prctl(PR_SET_SECCOMP, 0, 0, 0, 0)

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

Ссылки:
          www.mjmwired.net/kernel/Documentation/cgroups.txt
          libcg.sourceforge.net
          docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide
          www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html

Исходники этого и других постов со скриптами лежат тут — github.com/koder-ua. При использовании их, пожалуйста, ссылайтесь на koder-ua.blogspot.com.

Автор: konstantin danilov

Как допилить хардкордную Миранду.

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


Miranda на мой взгляд лучший менеджер обмена сообщениями по сети на платформе Windows.
Брать здесь http://www.miranda-im.org
Кстати 25 июня 2009 вышла версия 0.8.1

Кто сказал Qip?! Вы?! Хорошо я разрешаю вам не читать эту статью. Вон видите там виднеется лесопосадка? Ну, вот и идите, туда…

Минимальная конфигурация поставляемая с инсталлятором практически полностью меня устраивает. Но (как всегда это но) при общении с несколькими людьми одновременно клиент создает кучу окон и это реально раздражает. Ну, посудите сами — и так уже на панели задач место под запущенные вкладки приложений забито, а тут менеджер создает еще 3 и более, в зависимости от того сколько вы контактов открыли для общения.


Да операционная система может объединить сходные вкладки, но это не удобно и не эффективно — дергать каждый раз мышь от окна сообщения к окну панели или искать другое окно панели, если оно открыто и не лежит за другими окнами.

Но чем хороша Miranda, это своими плагинами — так что господа любители свистопердящего Qip идут все по тому же указанному направлению. Все свистоперделки у Miranda имеются в охренненом количестве. Но зачем оно мне?

А рассказать я хочу о действительно полезном плагине TabSRMM.
Качаем с сайта Miranda http://addons.miranda-im.org
Или c сайта автора плагина

Этот плагин позволяет объединить открытые окна контактов в одно с переключением через панельку вкладок в самом окне.

Устанавливается плагин простой распаковкой архива в папку Miranda
По умолчанию это «C:Program FilesMiranda IM»


После загрузки надо выбрать tabsrmm.dll настройках Options > Plugins вместо стандартного srmm.dll, дальше жмем Apply и закрываем окно настроек. Завершаем приложение и перезапускам.


Открываем несколько контактов и наслаждаемся общением. 🙂

Автор: Mario

Решение проблемы тормозов 2D в Linux для NVIDIA GeForce 8200.

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

Не так давно я обновил свой комп. Конфигурация:

AMD Athlon XP, 1833 MHz (11 x 167) 2500+
Epox EP-8RDA3, чипсет nVIDIA nForce2 Ultra 400
1024 Мб PC2700 DDR SDRAM
ATI Radeon X1650 Pro (RV530) (AGP)

сменилась на

AMD Athlon 64 X2 5400
2x2048Mb DDR2 PC2-6400(800Mhz) Kingston
MB Asus M3N78-VM, чипсет NVIDIA GeForce 8200 (MCP78S)
Встроенное в MB видео Nvidia 8200 — по сути немного урезанная наружная 8400.

Собственно конфигурация выбиралась из принципа: минимально подходящая конфигурация за приемлимую сумму.
Выбор именно на NVidia (хоть оно и встроенное) пал потому что на предыдущей конфигурации столкнулся с проблемами в Linux, в частности с кривым драйвером ATI который, ну никак не желал обеспечивать нормальную работу OpenGL в ALT Linux. http://www.altlinux.ru

Намучавщись c обновлениями и настройками, пару раз положив насмерть ALT Linux и начитавшись разных форумов сделал для себя вывод — в ближайшее время нормальных драйверов для ATI в Linux не увижу.

ALT Linux встал сразу и все вроде работает, в том числе и OpenGL — игрушки The Thing и Alice под Wine бегают очень даже замечательно. Остальное пока запустить нормально не удалось, потому как Wine имеет свои хитрости и ему надо «выкручивать руки». Однако наблюдались иногда тормоза уже в 2D режиме. В частности в Opera и Firefox при переключении вкладок, очень отчетливо было видно как оно прорисовывается. А иногда и обои рабочего стола отрисовывались очень медленно.

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

В общем никакие обновления до последних версий всего что было из сетевых репозиториев ALT Linux не помогло, все настройки я скрутил на минимум. Compiz вообще снес нафик, ибо бесполезная блажь и свистоперделка.

В результате упорных поисков была найдена нужная информация на ресурсе http://www.nvnews.net

Если задать в консоли:

# nvidia-settings -a InitialPixmapPlacement=2 -a GlyphCache=1

то все просто замечательно начинает бегать.

Объяснение:
1) InitialPixmapPlacement=2, который по умолчанию в состоянии 1, влияет на размещение пиксельно буфера в Оперативной памяти при значении 1, либо в памяти видеокарт при значении 2. Естественно второе дает существенный прирост, потому что скорость работы с собственной памятью, даже для встроенной видюхи (когда память выделена из общей памяти компа) все равно быстрее чем работа с системной памятью.

2) GlyphCache=1 размещает все шрифты в видеопамять. И это также дает некоторый прирост. Хотя у меня существенный прирост дал в основном первый пункт, но зачем отказываться от улучшения, так что прописал также.

Также в статье рекомендуется в /etc/X11/xorg.conf в раздел Screen section добавить следующее:

Option «PixmapCacheSize» «1000000»

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

У меня пока только осталась одна проблема — я не понял куда надо прописать для правильного автозапуска эти команды. В статье приводится рекомендация для прописывания в ~/.xinitrc/ ~/.kderc/ ~/.gnomerc, но у меня стоит KDE, ~/.xinitrc/ почему то отсутствует в ALT Linux, в ~/.kderc/ вообще что-то другое. В общем пока в папке автозапуска (можно получить доступ из меню запуска программ в ALT Linux) прописал ярлык с параметром «nvidia-settings -a InitialPixmapPlacement=2 -a GlyphCache=1». Работает конечно, но что-то в этом неправильное есть.

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

А кто не хочет? 😉

Автор: Mario

Честный обзор нетбука RoverBook NEO U800L, часть 2.

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


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

По умолчанию в субноуте стоял Linux под маркой Xandros http://www.xandros.com
Недаром данный конкретный субноут замаркирован буквой L в конце названия. Пощупав сей продукт и «порадовавшись» за скорость работы я решил что «так жить нельзя…»
По сей причине решил поставить Windows XP, надо сказать что сей шаг себя оправдал. Правда для установки потребовался внешний привод ATAPI, подключаемый через USB. Поскольку привода компакт-дисков в субноутах не водится, в связи с размерами, и, разумеется, в состав поставки ноутбука оно не входит. Выкрутился тем, что попросил на работе внешний привод на пару часов.

Что можно сказать про установку Windows? Готовьтесь к сильнейшим «эротическим» ощущениям! И все из-за нестандартного разрешения 800*480! Дело в том что в Vesa видео режиме (в котором собственно происходит установка ОС) устанавливается стандартный режим 800*600, но нижняя часть экрана естественно не показывается и обрезается, хотя инсталятору кажется что оно есть! Режим 640*480 субноут без драйвера не устанавливает. В результате имеем «все 33 удовольствия» — нижние кнопки не видно. Курсор мыши соответственно тоже не виден в нижней части экрана! Причем подключение через VGA разъем к внешнему монитору вас не спасет! На нем вы увидите те же 800*480, но растянутые на весь экран! В общем приходится вслепую перемещать фокус выделения по кнопкам, при помощи клавиши «Tab». Сколько при этом будет возвратов в прежнее меню… много в общем. Как результат вместо одного часа, установка Windows XP заняла у меня более 2-х часов, сопровождаемых крепкими непечатными словами. 🙂

Кроме Windows XP я установил на субноут Колибри ОС http://kolibrios.org
В этой части статьи я пока не буду описывать эту ОС (одним из разработчиков которой я кстати являюсь).

Данная часть статьи будет собственно посвящена внутреннему составу железа и Windows XP.
Кстати проблем с драйверами нет — вместе субноутом идет диск на котором еcть драйвера для Windows XP, но ОС даже Xandros там нету, как и драйверов под Linux. Весьма странная позиция для пользователя, ну да ладно оставим это на совести компании RoverBook.


Ну? вот Windows загружена — приступаю к прощупыванию начинки.


Нет, вы не о том подумали. %-)
Клава в субноуте тоже есть, но другая… 😉

Состав железа:
Итак что имеем согласно показаниям EVEREST http://www.lavalys.com

Суммарная информация:

Компьютер:
Тип компьютера Компьютер с ACPI
Операционная система Microsoft Windows XP Professional
Пакет обновления ОС Service Pack 2
Internet Explorer 6.0.2900.2180 (IE 6.0 SP2)
DirectX 4.09.00.0904 (DirectX 9.0c)

Системная плата:
Тип ЦП AMD Geode LX, 500 MHz (15 x 33)
Системная плата TECO Electric and Machinery Co., Ltd. TR2350
Чипсет системной платы Неизвестно
Системная память 487 Мб
Тип BIOS Insyde (05/29/08)

Дисплей:
Видеоадаптер AMD Custom Driver For 800×480 Panel (24 Мб)

Мультимедиа:
Звуковой адаптер AMD CS5536 (Geode companion) Audio Controller

Хранение данных:
Контроллер IDE Стандартный двухканальный контроллер PCI IDE
Дисковый накопитель FUJITSU MHW2060AT (60 Гб, 4200 RPM, Ultra-ATA/100)
Статус SMART жёстких дисков OK

Ввод:
Клавиатура Стандартная (101/102 клавиши) или клавиатура PS/2 Microsoft Natural
Мышь PS/2-совместимая мышь

Сеть:
Первичный адрес IP 127.0.0.1
Первичный ад
рес MAC 00-1A-13-B2-0D-D3
Сетевой адаптер Realtek RTL8139/810x Family Fast Ethernet NIC

Периферийные устройства:
Контроллер USB1 AMD CS5536 (Geode companion) OHCI USB Controller
Контроллер USB2 AMD CS5536 (Geode companion) EHCI USB Controller
USB-устройство Malata Notebook USB2.0 Camera
USB-устройство Универсальный USB концентратор
Батарея Адаптер блока питания (Майкрософт)
Батарея Батарея с ACPI-совместимым управлением Microsoft

Теперь немного подробнее:

Центральный процессор:
Тип ЦП AMD Geode LX, 500 MHz (15 x 33)
Наборы инструкций x86, MMX, 3DNow!
Кэш L1 кода 64 Кб
Кэш L1 данных 64 Кб
Кэш L2 128 Кб (On-Die, Full-Speed)

Производитель ЦП:
Фирма Advanced Micro Devices, Inc.

Системная плата
Свойства системной платы:
ID системной платы
Системная плата TECO Electric and Machinery Co., Ltd. TR2350

Свойства шины FSB:
Тип шины Intel GTL
Ширина шины 64 бит
Реальная частота 33 МГц
Эффективная частота 33 МГц
Пропускная способность 267 Мб/с

Память
Физическая память:
Всего 487 Мб
Занято 177 Мб
Свободно 309 Мб
Загрузка 36 %

Physical Address Extension (PAE):
Поддерживается ОС Да
Поддерживается ЦП Нет
Активный Нет

Физические данные дискового накопителя:
Производитель Fujitsu
Форм-фактор 2.5″
Форматированная ёмкость 60 Гб
Диски 1
Записываемые поверхности 2
Физические габариты 100 x 70 x 9.5 mm
Максимальный вес 96 g
Средняя задержка раскрутки 7.14 ms
Скорость вращения 4200 RPM
Среднее время поиска 12 ms
Переход с дорожки на дорожку 1.5 ms
Полное время поиска 22 ms
Интерфейс Ultra-ATA/100
Скорость данных 'буфер-контроллер' 100 Мб/с
Объём буфера 2 Мб

Как видно из характеристик — они подходят для Windows XP, но впритык. Как ни странно система шуршит вполне сносно, конечно на мой нетребовательный взгляд.

Однако кроме EVEREST есть и другие полезные программы — специалисты, вот и буду подробно их перечислять.

Первый специалист CPU-Z http://www.cpuid.com


Как видно это вторая программа, которая показывает частоту 33 МГц, но поскольку с большой вероятностью в субноуте использована DDR1, то на чтение частота будет 66 МГц, а вот на запись только 33 МГц.

Да и не удивляйтесь что памяти отображается не 512 Мб как заявлено производителем, потому как видео встроенное в процессор и память под него берется из основного ОЗУ.

У процессора отсутсвует поддержка SSE и это естественно плохо, но вопрос цены объясняет такие вещи.


Размер кэш памяти не так велик, но если честно на моем настольном AMD Duron 950 в свое время было не намного больше, так что вполне терпимо.


Никакой полезной информации о материнской плате не узнать.


Опять же только размер — бюджетное железо, экономят на всем.


Опять ничего. Даже обидно немного.

Следующий специалист по железу GPU-Z www.techpowerup.com


Результат вполне ожидаем — 3D ускоритель отсутсвует, потому чт
о «дешево и сердито».
Впрочем это можно увидеть и в свойствах системной утилиты «Средство диагностики Direct X».

Следующая утилита — специалист по температурам и вентиляторам SpeedFan http://www.almico.com


Специалист разглядел только один датчик, и это датчик жесткого диска, так что температуру процессора мы не узнаем.

Ну, как видим у специалистов негусто. Что же покажут стандартные утилиты системы?

Средство диагностики Direct X



Как видим ускорение 3D недоступно — ускоритель отсутсвует как вид!


Звук на основе кодека AC97 — бюджетный звук. Если хотите чтобы он хоть как-то звучал — подключите наушники или колонки.

Свойства видеоподситемы:


Из режимов доступны только 800*600, 800*480 и 640*480. Причем режим 800*600 работает только со скроллингом экрана, так что не считается, так как это не удобно, хоть и при этом все окна в виртуальный экран влезают по размерам.

Мультимедиа:

Чтобы без напрягов смотреть видео предлагаю воспользоваться плеером KMPlayer http://www.kmplayer.com который не требует внешних видеокодеков.


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

Правда Media Player Classic поставлемый с K-Lite Codec Pack http://www.codecguide.com/download_kl.htm тоже нетребователен, но все таки помедленней будет.

Игры

Да грустно конечно что нету 3D усорителя, и даже попытка заменить его софтварными ускорителями не поможет — система слишком хилая, чтобы эмулировать такие вещи.
Однако поиграть можно в те игры, которые не требуют наличия аппаратного 3D ускорителя:


Мой любимый Fallout 2! Пиво опционально, но вот мышка обязательна — играть посредством тачпада то еще извращение!


Мой не менее любимый StarCraft!


Ну, и естественно — Red Alert Aftermach!

Вообще то система, по идее, должна потянуть и Quake 2
и даже Half-Life в режиме софтварного движка. Однако не запускал, так что есть еще поле для экспериментов.

Видокамера
О встроенной видеокамере рассказывать особо нечего — даже для веб камеры весьма посредственная вещь! Разрешение 320*240 максимум и постоянное моргание в тщетных попытках сфокуссироваться. Малейшее движение или изменение положения источника света и все поплыло. Ну, что еще ожидать от бюджетной вещи!

Клавиатура
Клавиатура сокращенная, но в принципе можно привыкнуть. Некоторые клавиши эмулируются через удержание клавиши Fn (слева с низу), цифровая дополнительная клавиатура естественно отсутсвует.


Тачпад
Тачпад не очень точный и временами простое таскание мыши воспринимает как нажатие кнопки, так что для полноценной работы мышь весьма желательна.

Контроллер Wi-Fi
В субноуте присутствует контроллер Wi-Fi, но проверять мне его было не на чем, так что ничего не скажу конкретного.

Сеть Ethernet
Контроллер сети Ethernet обеспечивает нормальную работу 100 Мбит сети, но как я и описывал в первой части статьи — разъем сделан с нарушением стандарта и некоторые штеккеры сетевых шнуров в нем просто не держаться и вылетают. Не срабатывает защелка-фиксатор.
Например, такие штеккеры:

Питание
Аккумулятора хватает на 2,5-3 часа работы при средней нагрузке. Я, например, несколько раз занимаясь другими делами слушал на субноуте воспроизведение текста фанастических произведений, через CoolReader с использованием звукового движка Katerina. Часа на 2 хватает.

Вот в принципе и все для второй части, если соберусь написать третью часть, то это будет про установку Колибри ОС http://kolibrios.org и танцы с бубном по доводке (а куда же мы без бубна!).

Пока только пара фоток:


Синий экран загрузки в котором выбираются параметры.


Собственно сама ОС в загруженном состоянии.

Автор: Mario