tag:blogger.com,1999:blog-38764635946124120362024-03-14T01:15:51.661-07:00rfLinuxred_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.comBlogger29125tag:blogger.com,1999:blog-3876463594612412036.post-77814908349171330712021-01-31T05:10:00.000-08:002021-01-31T05:10:19.247-08:00Оптимизация: frame pointer omission<style type="text/css">
.source { display: inline-block; width: 8; }
.comment { color: #999999; font-style: italic; }
.pre { color: #000099; }
.string { color: #009900; }
.char { color: #009900; }
.float { color: #996600; }
.int { color: #999900; }
.bool { color: #000000; font-weight: bold; }
.type { color: #FF6633; }
.flow { color: #FF0000; }
.keyword { color: #990000; }
.operator { color: #663300; font-weight: bold; }
.operator { color: #663300; font-weight: bold; }
.mono { font-family: "Courier New", Courier, monospace; }
.addr { color: #e34adc; }
.insn { color:#800000; font-weight:bold; }
.bytes, .const { color:#008c00; }
.reg { color:#000080; }
.lit { color:#808030; }
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
</style>
<body>
<p align="justify">История началась с вынужденной переустановки Gentoo. Благодаря когда-то
давно где-то увиденной рекомендации из раза в раз в <span class="mono">make.conf</span> перекочёвывает переменная <span class="mono">CFLAGS</span> с аргументом <span class="mono">-fomit-frame-pointer</span>. Но что, собственно, он делает и нужен ли там вообще? Конец года 2020 хорошее время, чтобы в этом разобраться.</p>
<p align="justify">Чтобы понять, что делает <span class="mono">-fomit-frame-pointer</span>, вспомним кое-что о стеке. Рассмотрим простой код:
<pre style='color:#000000;background:#ffffff;'><span style='color:#004a43; '>#</span><span style='color:#004a43; '>include </span><span style='color:#800000; '><</span><span style='color:#40015a; '>stdio.h</span><span style='color:#800000; '>></span>
<span style='color:#800000; font-weight:bold; '>int</span> <span style='color:#400000; '>main</span><span class="lit">(</span><span style='color:#800000; font-weight:bold; '>int</span> argc<span class="lit">,</span> <span style='color:#800000; font-weight:bold; '>char</span><span class="lit">*</span><span class="lit">*</span> argv<span class="lit">)</span>
<span style='color:#800080; '>{</span>
<span style='color:#603000; '>printf</span><span class="lit">(</span><span style='color:#800000; '>"</span><span style='color:#0000e6; '>Hello </span><span style='color:#007997; '>%s</span><span style='color:#0f69ff; '>\n</span><span style='color:#800000; '>"</span><span class="lit">,</span> <span style='color:#800000; '>"</span><span style='color:#0000e6; '>world</span><span style='color:#800000; '>"</span><span class="lit">)</span><span style='color:#800080; '>;</span>
<span style='color:#800080; '>}</span>
</pre>
Что происходит при вызове функции <span class="mono">printf()</span> (да и самой <span class="mono">main()</span> тоже)?</p>
<p align="justify">В точности следующее:
<pre style='color:#000000;background:#ffffff;'><span class="bytes">00001199</span> <span class="lit"><</span>main<span class="lit">></span><span class="lit">:</span>
<span class="addr">    1199:</span> <span class="bytes">8d 4c 24 04</span> <span class="insn">lea</span> <span class="const">0x4</span><span class="lit">(</span><span class="lit">%</span><span class="reg">esp</span><span class="lit">)</span><span class="lit">,</span><span class="lit">%</span><span class="reg">ecx</span>
<span class="addr">    119d:</span> <span class="bytes">83 e4 f0</span> <span class="insn">and</span> $<span class="const">0xfffffff0</span><span class="lit">,</span><span class="lit">%</span><span class="reg">esp</span>
<span class="addr">    11a0:</span> <span class="bytes">ff 71 fc</span> <span class="insn">pushl</span> <span class="lit">-</span><span class="const">0x4</span><span class="lit">(</span><span class="lit">%</span><span class="reg">ecx</span><span class="lit">)</span>
<span class="addr">    11a3:</span> <span class="bytes">55</span> <span class="insn">push</span> <span class="lit">%</span><span class="reg">ebp</span>
<span class="addr">    11a4:</span> <span class="bytes">89 e5</span> <span class="insn">mov</span> <span class="lit">%</span><span class="reg">esp</span><span class="lit">,</span><span class="lit">%</span><span class="reg">ebp</span>
<span class="addr">    11a6:</span> <span class="bytes">53</span> <span class="insn">push</span> <span class="lit">%</span><span class="reg">ebx</span>
<span class="addr">    11a7:</span> <span class="bytes">51</span> <span class="insn">push</span> <span class="lit">%</span><span class="reg">ecx</span>
<span class="addr">    11a8:</span> <span class="bytes">e8 2f 00 00 00</span> <span class="insn">call</span> <span class="addr">11dc</span> <span class="lit"><</span>__x86.get_pc_thunk.ax<span class="lit">></span>
<span class="addr">    11ad:</span> <span class="bytes">05 53 2e 00 00</span> <span class="insn">add</span> $<span class="const">0x2e53</span><span class="lit">,</span><span class="lit">%</span><span class="reg">eax</span>
<span class="addr">    11b2:</span> <span class="bytes">83 ec 08</span> <span class="insn">sub</span> $<span class="const">0x8</span><span class="lit">,</span><span class="lit">%</span><span class="reg">esp</span>
<span class="addr">    11b5:</span> <span class="bytes">8d 90 08 e0 ff ff</span> <span class="insn">lea</span> <span class="lit">-</span><span class="const">0x1ff8</span><span class="lit">(</span><span class="lit">%</span><span class="reg">eax</span><span class="lit">)</span><span class="lit">,</span><span class="lit">%</span><span class="reg">edx</span>
<span class="addr">    11bb:</span> <span class="bytes">52</span> <span class="insn">push</span> <span class="lit">%</span><span class="reg">edx</span>
<span class="addr">    11bc:</span> <span class="bytes">8d 90 0e e0 ff ff</span> <span class="insn">lea</span> <span class="lit">-</span><span class="const">0x1ff2</span><span class="lit">(</span><span class="lit">%</span><span class="reg">eax</span><span class="lit">)</span><span class="lit">,</span><span class="lit">%</span><span class="reg">edx</span>
<span class="addr">    11c2:</span> <span class="bytes">52</span> <span class="insn">push</span> <span class="lit">%</span><span class="reg">edx</span>
<span class="addr">    11c3:</span> <span class="bytes">89 c3</span> <span class="insn">mov</span> <span class="lit">%</span><span class="reg">eax</span><span class="lit">,</span><span class="lit">%</span><span class="reg">ebx</span>
<span class="addr">    11c5:</span> <span class="bytes">e8 66 fe ff ff</span> <span class="insn">call</span> <span class="addr">1030</span> <span class="lit"><</span>printf@plt<span class="lit">></span>
<span class="addr">    11ca:</span> <span class="bytes">83 c4 10</span> <span class="insn">add</span> $<span class="const">0x10</span><span class="lit">,</span><span class="lit">%</span><span class="reg">esp</span>
<span class="addr">    11cd:</span> <span class="bytes">b8 00 00 00 00</span> <span class="insn">mov</span> $<span class="const">0x0</span><span class="lit">,</span><span class="lit">%</span><span class="reg">eax</span>
<span class="addr">    11d2:</span> <span class="bytes">8d 65 f8</span> <span class="insn">lea</span> <span class="lit">-</span><span class="const">0x8</span><span class="lit">(</span><span class="lit">%</span><span class="reg">ebp</span><span class="lit">)</span><span class="lit">,</span><span class="lit">%</span><span class="reg">esp</span>
<span class="addr">    11d5:</span> <span class="bytes">59</span> <span class="insn">pop</span> <span class="lit">%</span><span class="reg">ecx</span>
<span class="addr">    11d6:</span> <span class="bytes">5b</span> <span class="insn">pop</span> <span class="lit">%</span><span class="reg">ebx</span>
<span class="addr">    11d7:</span> <span class="bytes">5d</span> <span class="insn">pop</span> <span class="lit">%</span><span class="reg">ebp</span>
<span class="addr">    11d8:</span> <span class="bytes">8d 61 fc</span> <span class="insn">lea</span> <span class="lit">-</span><span class="const">0x4</span><span class="lit">(</span><span class="lit">%</span><span class="reg">ecx</span><span class="lit">)</span><span class="lit">,</span><span class="lit">%</span><span class="reg">esp</span>
<span class="addr">    11db:</span> <span class="bytes">c3</span> <span class="insn">ret</span>
</pre>
В строках 11bb и 11c2 в стек запихиваются аргументы для <span class="mono">printf()</span> и на момент после исполнения инструкции call стек принимает следующий вид:
<br/><br/>
<center>
<table width="300px" style='border-bottom:none;border-top:none'>
<tbody>
<tr style='border-top:none'>
<td align="center" width="35%" style='border-top:none'>...</td>
<td align="center" style='border-top:none'>...</td>
</tr>
<tr>
<td width="35%">0xXXXXfffb</td>
<td align="center">arg1 [ebp + 12]</td>
</tr>
<tr>
<td width="35%">0xXXXXfff7</td>
<td align="center">arg0 [ebp + 8]</td>
</tr>
<tr>
<td width="35%">0xXXXXfff3</td>
<td align="center">return addr [ebp + 4]</td>
</tr>
<tr>
<td width="35%">0xXXXXffef</td>
<td align="center">previous ebp [ebp + 0]</td>
</tr>
<tr style='border-bottom:none'>
<td align="center" width="35%" style='border-bottom:none'>...</td>
<td align="center" style='border-bottom:none'>...</td>
</tr>
</tbody>
</table>
</center>
<br/>
Если кому-то интересно, что происходит в строках 11a8-11ad, то это работа PIC (position-independent code), в котором данные адресуются относительно текущей инструкции кода. В x86-64 возможна непосредственная адресация относительно <span class="mono">rip</span>, но в х86 такого режима не было. Да-да, это 32-битный код для х86. Так было проще продемонстрировать передачу аргументов через стек. Потом поясним, почему. PIC в gcc включен по умолчанию. Запустим gcc с флагом <span class="mono">-fno-pic</span>, и вывод будет выглядеть уже иначе:
<pre style='color:#000000;background:#ffffff;'>
<span class="addr">    11ad:</span> <span class="bytes">68 08 20 00 00</span> <span class="insn">push</span> $<span class="const">0x2008</span>
<span class="addr">    11b2:</span> <span class="bytes">68 0e 20 00 00</span> <span class="insn">push</span> $<span class="const">0x200e</span>
<span class="addr">    11b7:</span> <span class="bytes">e8 fc ff ff ff</span> <span class="insn">call</span> <span class="addr">11b8</span> <span class="lit"><</span>main<span class="lit">+</span><span class="reg">0x1f</span><span class="lit">></span>
</pre>
что воспринимается заметно проще. Но вернёмся назад к нашей <span class="mono">printf()</span>. Наверняка, она использует какие-то локальные переменные. Много локальных переменных. Регистров общего назначения у процессоров x86 не сказать, чтобы много. Едва ли их хватит на все нужды <span class="mono">printf()</span> и эти переменные будут размещены на стеке. В самом начале выполнения <span class="mono">printf()</span> на вершине стека находится адрес, куда будет передано управление после того, как в теле <span class="mono">printf()</span> процессор выполнит инструкцию <span class="mono">ret</span>. Для того, чтобы зарезервировать на стеке место для локальных переменных <span class="mono">printf()</span>, надо вычесть нужное число байт из <span class="mono">esp</span> - указателя на вершину стека, сдвинув его таким образом вперёд. Далее, обращаться к локальным переменным <span class="mono">printf()</span> сможет, например, по смещению относительно <span class="mono">esp</span>. Но это не всегда удобно, ведь по ходу дела программа может затолкать в стек ещё какие-то данные, не связанные с вызовом подпрограмм, значение <span class="mono">esp</span> изменится и смещения поплывут. Однако есть ещё один регистр <span class="mono">ebp</span>. Как правило, в прологе функции в нём сохраняется содержимое регистра <span class="mono">esp</span>, которое <span class="mono">printf()</span> "унаследует" от вызывающего кода. На протяжении выполнения <span class="mono">printf()</span> содержимое <span class="mono">ebp</span> не изменится. Вот относительно него-то и можно адресовать локальные переменные. Пролог типичной функции выглядит очень просто:
<pre style='color:#000000;background:#ffffff;'><span class="addr"> XXXX:</span> <span class="bytes">55</span> <span class="insn">push</span> <span class="lit">%</span><span class="reg">ebp</span>
<span class="addr">    XXXX:</span> <span class="bytes">89 e5</span> <span class="insn">mov</span> <span class="lit">%</span><span class="reg">esp</span><span class="lit">,</span><span class="lit">%</span><span class="reg">ebp</span>
</pre>
Действия, вполняемые этим кодом, отменяются единственной инструкцией 32-разрядных процессоров x86 <span class="mono">leave</span>, которая эквивалентна этому коду:
<pre style='color:#000000;background:#ffffff;'><span class="addr">    XXXX:</span> <span class="bytes">89 ec</span> <span class="insn">mov</span> <span class="lit">%</span><span class="reg">ebp</span><span class="lit">,</span><span class="lit">%</span><span class="reg">esp</span>
<span class="addr">    XXXX:</span> <span class="bytes">5d</span> <span class="insn">pop</span> <span class="lit">%</span><span class="reg">ebp</span>
</pre>
Невероятно, но факт: существует инструкция <span class="mono">enter</span>, которая настраивает фрейм, то есть, соответствует прологу. Однако <s>этот факт тщательно скрывается</s> её эффективность оказалась ниже, чем хотелось бы. Так что она не используется в коде, генерируемом gcc. А с <span class="mono">leave</span> всё в порядке. Потому и видим мы её, а не комбинацию мув-поп.
</p>
<p align="justify">Казалось бы, к чему всё это? Именно к самой сути флага <span class="mono">-fomit-frame-pointer</span>, ведь содержимое <span class="mono">ebp</span> и есть тот самый указатель на фрейм, а наш флаг просто подавляет генерацию пролога и, соответственно, эпилога. Имеет ли такая оптимизация смысл? Ведь в сумме мы экономим самое большее 6 байтов и какое-то мизерное число тактов на грани статистической погрешности. Этот аргумент звучит не очень убедительно, но вряд ли разработчики компиляторов стали бы заморачиваться, если смысла совсем не было - да, MSVC тоже умеет элиминировать указатель на фреймы ключом /Oy. Действительно, если функция достаточно мала, настолько, что пролог и эпилог составляют ощутимый процент её тела, и в то же время вызывается она очень часто, выигрыш может стоить свеч. Хотя тут уже впору задуматься об инлайнинге. Может показаться, что эта оптимизация в любом случае не навредит, хотя бы и толку от неё было не особо много. На самом деле она усложняет отладку. Для компилятора заменить все ссылки на содержимое стека относительно <span class="mono">ebp</span> смещениями от <span class="mono">esp</span> не особо-то большое дело. Но при этом будет утрачена или существенно затруднена возможность следовать за выполнением кода по стеку вызовов. Отладчик всегда может без проблем найти ссылку на вызывающий код по содержимому <span class="mono">ebp</span>. Ведь как мы помним на вершине стека хранится адрес, принадлежащий функции, которой надлежит вернуть управление по окончании работы. Так как содержимое <span class="mono">ebp</span> больше не указывает гарантированно на вершину стека на момент вызова данной функции, то отладчик поставлен в тупик. Палка о двух концах. Но, кстати, вдобавок мы ещё получили свободный регистр... Когда их не так много и ещё один дополнительный регистр - это плюс 16% к объёму регистровой памяти, то возможно результат уже более ощутим. На процессорах x86 это близко к истине. Но не на amd64, где регистры широкие и их уже побольше.</p>
<p align="justify">Ещё один нюанс флага <span class="mono">-fomit-frame-pointer</span> состоит в том, что он включает оптимизацию, но не гарантирует её. Вот что по этому поводу можно прочесть в первоисточнике, man-странице gcc:
<pre>
-fomit-frame-pointer
Omit the frame pointer in functions that don't need one. This avoids the
instructions to save, set up and restore the frame pointer; on many
targets it also makes an extra register available.
On some targets this flag has no effect because the standard calling
sequence always uses a frame pointer, so it cannot be omitted.
Note that -fno-omit-frame-pointer doesn't guarantee the frame pointer is
used in all functions. Several targets always omit the frame pointer in
leaf functions.
Enabled by default at -O and higher.
</pre>
В частности, пролог функции не может быть элиминирован в том случае, когда функция вызывает <span class="mono">alloca()</span>. <span class="mono">alloca()</span> не особо популярна и обладает неоднозначной репутацией, но тем не менее. Между тем, <span class="mono">alloca()</span>, выделяя память на стеке, делает это куда быстрее, чем <span class="mono">malloc()</span>, не требует парного вызова <span class="mono">free()</span>, так как память освобождается автоматически при выходе из стекового фрейма. Впрочем, <span class="mono">alloca()</span> не может использоваться в inline-функциях и пригодна для выделения лишь небольших блоков памяти на несколько сотен байтов максимум при гарантии, что указатель на блок не будет использован после возврата из функции, в которой был выделен данный блок. С точки зрения безопасности элиминация прологов функци сокращает attack surface для перехвата управления, когда злонамеренный код перезаписывает пролог инструкциями ветвления, так называемый трамплин (trampoline)[1].
</p>
<p>
Условия, при которых компилятор не будет генерировать пролог и эпилог функции, могут в частности, включать следующее:
<ul>
<li>Данная функция - тупиковая (leaf function). То есть, она находится на вершине стека вызовов и в свою очередь не вызывает другие функции;</li>
<li>Не используются исключения;</li>
<li>Данная функция не содержит вызовов других функций, параметры которых передаются на стеке;</li>
<li>Данная функция не принимает аргументов.</li>
</ul>
</p>
<p>Возвращаясь к вопросу, почему в целях демонстрации использовался 32-разрядный код, можно сказать следующее. Linux ABI на amd64 требует передачи параметров в регистрах. Как уже упоминалось, в amd64 регистров общего назначения больше. Добиться от кода использования стека, соответственно, несколько проблематичнее, в противоположность ABI IA-32.</p>
<p>Ссылки:
<ol>
<li>Learning Linux binary analysis. Ryan O'Neill. 2016</li>
<li><a href="https://www.agner.org/optimize/#manuals" >Agner Vog's optimization manuals</a></li>
<li><a href="https://raw.githubusercontent.com/wiki/hjl-tools/x86-psABI/intel386-psABI-1.1.pdf">System V Application Binary Interface. Intel386 Architecture Processor Supplement, Version 1.1</a></li>
</ol></p>
</body>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-4674598954664782372014-12-01T03:16:00.001-08:002014-12-01T03:18:35.370-08:00Объекты ядра - kobject<style type="text/css">
.source { display: inline-block; width: 8; }
.comment { color: #999999; font-style: italic; }
.pre { color: #000099; }
.string { color: #009900; }
.char { color: #009900; }
.float { color: #996600; }
.int { color: #999900; }
.bool { color: #000000; font-weight: bold; }
.type { color: #FF6633; }
.flow { color: #FF0000; }
.keyword { color: #990000; }
.operator { color: #663300; font-weight: bold; }
.operator { color: #663300; font-weight: bold; }
.mono { font-family: "Courier New", Courier, monospace; }
td { border-top: 1px solid black; border-collapse: collapse; }
</style>
По мотивам <a href="http://lwn.net/Articles/51437/">http://lwn.net/Articles/51437/</a> и Documentation/kobject.txt.<br />
<div align="right"><i>Всё, что вы никогда не хотели знать об объектах, множествах и типах объектов ядра.</i></div>
<p align="justify">
Отчасти сложность понимания модели драйверов - и объектов ядра (kobject), абстракции, на которой построена эта модель - в том, что здесь нет ясного подхода, с которого можно было бы начать. Работа с объектами требует понимания других типов ядра, которые взаимосвязаны и ссылаются друг на друга. Чтобы упростить объяснение, мы попытаемся рассмотреть многосторонний подход и начнём с определения терминов, по ходу дела проясняя детали. Вот краткие определения сущностей, с которыми мы будем иметь дело:
<ul><li>
<p align="justify"><b>kobject (объект ядра)</b> - это объект типа <span class="mono">struct kobject</span> (внезапно :)). Объект имеет имя и счётчик ссылок. Также у него есть указатель на родительский объект, что позволяет огранизовать объекты в иерархию, определённый тип и, обычно, некоторое представление в виртуальной файловой системе sysfs.</p>
<p align="justify">В общем и в целом, сами по себе объекты интереса не представляют. Но они внедряются в другие структуры, с которыми код драйвера имеет дело</p>
<p align="justify">Подобного рода структура НИКОГДА не должна содержать более одного внедрённого объекта. Если нарушить это правило, подсчёт ссылок объекта наверняка окажется поломанным и ваш код будет работать неправильно. Так что не делайте этого.</p>
</li><li>
<p align="justify"><b>ktype</b> - тип объекта. Каждая структура, с которой ассоциирован объект ядра (далее, если явно не оговорено иное, просто объект для краткости, kobject), должна принадлежать какому-то типу ktype. Принадлежность объекта к типу ktype определяет, что происходит с объектом при его создании или уничтожении.</p>
</li><li>
<p align="justify"><b>kset (множество)</b> - группа объектов. Объекты, входящие в данное множество, могут принадлежать к одному или разным типам. Множество kset - это базовый контейнер для группы объектов. Множества kset имеют собственные, ассоциированные с ними объекты, но этой деталью реализации можно смело пренебречь, т.к. с этими объектами работает сам код поддержки kset.</p>
<p align="justify">Когда вы видите директорию в sysfs, забитую другими директориями, в общем случае эти директории представляют объекты, принадлежащие одному и тому же множеству.</p>
</li></ul>
<p align="justify">Далее мы увидим, как создавать эти типы и манипулировать ими. Мы начнём с простого и выйдем снова на объекты.</p>
<h4>Внедрение объектов</h4>
<p align="justify">Ситуация, когда код ядра создаёт самостоятельные объекты, достаточно редка за исключением одного случая, который будет рассмотрен ниже. Вместо этого объекты используются для управления доступом к более сложным подсистемно-специфичным структурам. Таким образом, объект встраивается в управляемую структуру. Если вы привыкли думать в манере объектно-ориентированной парадигмы, kobject - это базовый абстрактный класс, которому наследуют другие классы. kobject реализует некоторые вещи, которые не очень нужны им самим, но очень полезные другим объектам. Язык C не позволяет напрямую пользоваться наследованием и чтобы компенсировать это ограничение, используется внедрение одних структур в другие.</p>
<p align="justify">(Для тех, кто знаком с реализацией списков в ядре, это аналогично тому, как сама по себе структура <a href="http://rflinux.blogspot.com/2009/05/linux.html">"list_head"</a> обычно бесполезна, но для создания списков объектов её внедряют в другие структуры.)</p>
<p align="justify">Итак, для примера код в подсистеме пользовательского ввода-вывода UIO из drivers/uio/uio.c пользуется такой структурой, определяющей регион памяти, присвоенный UIO-устройству:
<pre><span class="keyword"> struct</span> uio_map<span class="operator"> {</span><span class="keyword">
struct</span> kobject kobj<span class="operator">;</span><span class="keyword">
struct</span> uio_mem<span class="operator"> *</span>mem<span class="operator">;
};</span></pre>
</p>
<p align="justify">Работая с объектом типа <span class="mono">struct uio_map structure</span>, для доступа к ассоциированному объекту ядра вы просто используете поле kobj. В коде, который работает с самими объектами ядра, а не инкапсулирующими их типами возникает проблема, как по указателю на внедрённый объект выяснить, кому принадлежит данный объект? Вы должны избегать трюков и не подразумевать, например, что структура kobject находится в начале той структуры, в которую этот kobject внедрён. Вместо этого используйте макрос <span class="mono">container_of()</span> из <linux/kernel.h>:
<pre> container_of<span class="operator">(</span>pointer<span class="operator">,</span> type<span class="operator">,</span> member<span class="operator">)</span></pre>
где:
<ul>
<li>"pointer" указатель на внедрённый kobject,</li>
<li>"type" тип инкапсулирующего объекта и</li>
<li>"member" имя поля структуры, на которую указывает "pointer".</li>
</ul>
</p>
<p align="justify">Значение, возвращаемое <span class="mono">container_of()</span> - это указатель на соответствующий тип-контейнер. Например, указатель "kp" на <span class="mono">struct kobject</span>, внедрённый внутрь <span class="mono">struct uio_map</span> может быть преобразован к типу-контейнеру <span class="mono">struct uio_map</span> таким образом:
<pre><span class="keyword"> struct</span> uio_map<span class="operator"> *</span>u_map<span class="operator"> =</span> container_of<span class="operator">(</span>kp<span class="operator">,</span><span class="keyword"> struct</span> uio_map<span class="operator">,</span> kobj<span class="operator">);</span></pre>
</p>
<p align="justify">Для удобства программисты часто определяют небольшие макросы "обратного приведения к типу" указателя на объект ядра к инкапсулирующему типу:
<pre><span class="keyword"> struct</span> uio_map<span class="operator"> {</span><span class="keyword">
struct</span> kobject kobj<span class="operator">;</span><span class="keyword">
struct</span> uio_mem<span class="operator"> *</span>mem<span class="operator">;
};</span><span class="pre">
#define to_map(map) container_of(map, struct uio_map, kobj)</span></pre>
</p>
<p align="justify">Здесь "map" это указатель на поле типа <span class="mono">struct kobject</span> в нашей структуре. Вот как этот макрос используется:
<pre><span class="keyword"> struct</span> uio_map<span class="operator"> *</span>map<span class="operator"> =</span> to_map<span class="operator">(</span>kobj<span class="operator">);</span></pre>
</p>
<h4>Инициализация объектов</h4>
<p align="justify">Код, создающий объект, должен его инициализировать. Некоторые внутренние поля инициализируются обязательным вызовом <span class="mono">kobject_init()</span>:
<pre><span class="type"> void</span> kobject_init<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> struct</span> kobj_type<span class="operator"> *</span>ktype<span class="operator">);</span></pre>
</p>
<p align="justify">Для объекта должен быть создан тип ktype, так как каждый объект должен иметь тип kobj_type. После вызова <span class="mono">kobject_init()</span>, чтобы зарегистрировать объект в sysfs, необходимо вызвать функцию <span class="mono">kobject_add()</span>:
<pre><span class="type"> int</span> kobject_add<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> struct</span> kobject<span class="operator"> *</span>parent<span class="operator">,</span><span class="keyword"> const</span><span class="type"> char</span><span class="operator"> *</span>fmt<span class="operator">, ...);</span></pre>
</p>
<p align="justify">Этим вызовом объекту присваивается родительский объект и имя. Если объект должен входить в какое-то множество, то перед вызовом <span class="mono">kobject_add()</span> поле <span class="mono">kobj->kset</span> должно быть заполнено соответствующим образом. Если объект сам ассоциирован со множеством kset то указатель на родительский объект может быть NULL, а родительским объектом станет само множество kset.
</p>
<p align="justify">С момента регистрации объекта в ядре его имя не должно изменяться напрямую. Если необходимо переименовать объект, используйте функцию <span class="mono">kobject_rename()</span>:
<pre><span class="type"> int</span> kobject_rename<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> const</span><span class="type"> char</span><span class="operator"> *</span>new_name<span class="operator">);</span></pre>
</p>
<p align="justify"><span class="mono">kobject_rename</span> не держит блокировок и не определяет, как должно выглядеть имя объекта, так что синхронизацию и прочие проверки должен обеспечивать пользователь.
</p>
<p align="justify">Есть ещё одна функция, <span class="mono">kobject_set_name()</span>, но она устарела и удаляется из кода. Если ваш код содержит этот вызов, вам стоит исправить код.
</p>
<p align="justify">Чтобы получить доступ к имени объекта, используйте функцию <span class="mono">kobject_name()</span>:
<pre><span class="keyword"> const</span><span class="type"> char</span><span class="operator"> *</span>kobject_name<span class="operator">(</span><span class="keyword">const struct</span> kobject<span class="operator"> *</span> kobj<span class="operator">);</span></pre>
</p>
<p align="justify">Для того, чтобы проинициализировать и зарегистрировать объект в ядре, предусмотрена вспомогательная функция с достаточно неожиданным (<i>уроки искромётного юмора от К-Х</i> - перев.) именем <span class="mono">kobject_init_and_add()</span>:
<pre><span class="type"> int</span> kobject_init_and_add<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> struct</span> kobj_type<span class="operator"> *</span>ktype<span class="operator">,</span><span class="keyword">
struct</span> kobject<span class="operator"> *</span>parent<span class="operator">,</span><span class="keyword"> const</span><span class="type"> char</span><span class="operator"> *</span>fmt<span class="operator">, ...);</span></pre>
</p>
<p align="justify">Смысл аргументов тот же, что для функций <span class="mono">kobject_init()</span> и <span class="mono">kobject_add()</span>, описанных выше.</p>
<h4>События uevent</h4>
<p align="justify">После того, как объект был зарегистрирован, необходимо уведомить всех о том, что мы создали объект. Сделать это можно с помощью функции <span class="mono">kobject_uevent()</span>:
<pre><span class="type"> int</span> kobject_uevent<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> enum</span> kobject_action action<span class="operator">);</span></pre>
</p>
<p align="justify">При добавлении объекта используйте флаг <span class="mono">KOBJ_ADD</span> в качестве аргумента action. Это должно делаться только после того, как были проинициализированы атрибуты дочерних объектов, потому что при уведомлении о новом объекте юзерспейс сразу же обратиться к этим данным.
</p>
<p align="justify">Когда объект удаляется из ядра (детали, как это делается, см. ниже), создаётся событие uevent для соответствующего действия <span class="mono">KOBJ_REMOVE</span>. Пользоателю нет нужды об этом волноваться, т.к. всю работу берёт на себя подсистема объектов.
</p>
<h4>Подсчёт ссылок</h4>
<p align="justify">Одна из ключевых функций объектов - это подсчёт ссылок на то, во что он внедрён. До тех пор, пока есть ссылки на данный объект, объект (и код, который его поддерживает) должен существовать. Низкоуровневые функции для манипуляций счётчиком ссылок на объект:
<pre><span class="keyword"> struct</span> kobject<span class="operator"> *</span>kobject_get<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">);</span><span class="type">
void</span> kobject_put<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">);</span></pre>
</p>
<p align="justify">Успешный вызов <span class="mono">kobject_get()</span> увеличит счётчик ссылок на объект и вернёт указатель на него.</p>
<p align="justify">Когда ссылка больше не нужна, вызов <span class="mono">kobject_put()</span> уменьшит счётчик и, вероятно, освободит объект. Обратите внимание, что <span class="mono">kobject_init()</span> устанавливает счётчик ссылок в 1, так что код, который проинициализировал объект в конечном счёте должен будет вызвать <span class="mono">kobject_put()</span>, чтобы освободить объект.</p>
<p align="justify">Т.к. по своей природе объекты динамичны, они не должны объявляться статически или на стеке. Вместо этого они должны создаваться динамически. В будущем ядро будет содержать код, выявляющий статически созданные объекты и предупреждающий программиста о неправильном использовании объектов.</p>
<p align="justify">Если всё, что вам нужно, это подсчёт ссылок на вашу структуру, используйте kref, а не kobject. Последний будет избыточен для одного лишь аудита объектов. Чтобы узнать больше о kref, прочтите файл Documentation/kref.txt в дереве кода ядра Linux.</p>
<h4>Создание "простых" объектов</h4>
<p align="justify">Иногда программисту нужно просто создать директорию в иерархии sysfs без использования множеств, функций чтения/сохранения и прочих деталей. Это единственное исключение, когда нужно создавать самостоятельный объект. Чтобы создать его, используйте следующую функцию:
<pre><span class="keyword"> struct</span> kobject<span class="operator"> *</span>kobject_create_and_add<span class="operator">(</span><span class="type">char</span><span class="operator"> *</span>name<span class="operator">,</span><span class="keyword"> struct</span> kobject<span class="operator"> *</span>parent<span class="operator">);</span></pre>
</p>
<p align="justify">Эта функция создаст объект и поместит в sysfs директорию под директорией, которая соответствует родительскому объекту. Чтобы создать простые атрибуты объекта, используйте вызов:
<pre><span class="type"> int</span> sysfs_create_file<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> struct</span> attribute<span class="operator"> *</span>attr<span class="operator">);</span></pre>
или
<pre><span class="type"> int</span> sysfs_create_group<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> struct</span> attribute_group<span class="operator"> *</span>grp<span class="operator">);</span></pre>
</p>
<p align="justify">Оба типа атрибутов, которые здесь используются, для объекта, созданного с помощью <span class="mono">kobject_create_and_add()</span>, могут иметь тип kobj_attribute, так что создавать новые пользовательские атрибуты необязательно.
</p>
<p align="justify">См. примеры реализации простого объекта и атрибутов в модулях в samples/kobject/kobject-example.c.
</p>
<h4>Типы ktype и деструкторы объекта</h4>
<p align="justify">До сих пор мы не касались одного важного момента. Что происходит с объектом, когда его счётчик ссылок обнуляется? Обычно код, создавший объект, не знает, когда наступит такой момент; если бы это было возможно, то особого смысла в использовании объектов просто не было бы. Когда мы имеем дело с sysfs даже объекты с предсказуемым жизненным циклом становятся сложными в этом плане, потому что какая-то другая часть ядра может получить ссылку на любой объект, зарегистрированный в системе.
</p>
<p align="justify">Конечным итогом этой ситуации является то, что объект не может быть освобождён до тех пор, пока счётчик ссылок на него не равен нулю. Подсчёт ссылок не находится под прямым контролем того кода, который создал объект. Код, породивший объект, должен быть асинхронно уведомлён об обнулении счётчика ссылок на данный объект.
</p>
<p align="justify">Создав свой объект с помощью <span class="mono">kobject_add()</span> вы никогда не должны использовать на нём <span class="mono">kfree()</span>, чтобы напрямую осводобить объект. Единственный безопасный способ сделать это - использовать <span class="mono">kobject_put()</span>. Вызов <span class="mono">kobject_put()</span> сразу после <span class="mono">kobject_init()</span> является хорошим правилом, которое позволит предотвратить возможные ошибки.
</p>
<p align="justify">Уведомление об обнулении счётчика ссылок происходит с помощью метода объекта - <span class="mono">release(). Обычно это выглядит как-то так:
<pre><span class="type"> void</span> my_object_release<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">)
{</span><span class="keyword">
struct</span> my_object<span class="operator"> *</span>mine<span class="operator"> =</span> container_of<span class="operator">(</span>kobj<span class="operator">,</span><span class="keyword"> struct</span> my_object<span class="operator">,</span> kobj<span class="operator">);</span><span class="comment">
/* Perform any additional cleanup on this object, then... */</span>
kfree<span class="operator">(</span>mine<span class="operator">);
}</span></pre>
<p align="justify">Важный момент, которым нельзя пренебречь: каждый объект должен иметь метод <span class="mono">release()</span> и объект должен находиться в непротиворечивом состоянии ровно до тех пор, пока не вызван этот метод. Код, нарушающий эти требования нарушены, дефективен. ОБратите внимание, ядро предупредит вас, если объект не имеет метода <span class="mono">release()</span>. Не пытайтесь избавиться от этих предупреждений, создавая пустые заглушки вместо реально работающего метода; если вы попытаетесь провернуть такой трюк, вам придётся иметь дело с беспощадным мэйнтейнером подсистемы kobject.
</p>
<p align="justify">Имейте в виду, имя объекта доступно из метода-деструктора, но оно НЕ ДОЛЖНО изменяться внутри кода деструктора. В противном случае это может привести к утечке памяти в подсистеме управления объектов, а такие вещи печалят пользователей.
</p>
<p align="justify">Любопытным является то обстоятельство, что метод <span class="mono">release()</span> не хранится внутри самого объекта; вместо этого деструктор ассоциирован с соответствующим типом объектов - ktype. Давайте посмотрим на структуру <span class="mono">struct kobj_type</span>:
<pre><span class="keyword"> struct</span> kobj_type<span class="operator"> {</span><span class="type">
void</span><span class="operator"> (*</span>release<span class="operator">)(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">);</span><span class="keyword">
const struct</span> sysfs_ops<span class="operator"> *</span>sysfs_ops<span class="operator">;</span><span class="keyword">
struct</span> attribute<span class="operator"> **</span>default_attrs<span class="operator">;</span><span class="keyword">
const struct</span> kobj_ns_type_operations<span class="operator"> *(*</span>child_ns_type<span class="operator">)(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">);</span><span class="keyword">
const</span><span class="type"> void</span><span class="operator"> *(*</span><span class="keyword">namespace</span><span class="operator">)(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">);
};</span></pre>
</p>
<p align="justify">Эта структура описывает определённый тип объектов (если быть более точным, тип инкапсулирующего kobject объекта). Каждый объект должен принадлежать к какому-то типу, описанному структурой kobj_type; указатель на структуру этого типа должен быть передан при вызове функции <span class="mono">kobject_init()</span> или <span class="mono">kobject_init_and_add()</span>.
</p>
<p align="justify">Поле release в <span class="mono">struct kobj_type</span> это, конечно, указатель на метод <span class="mono">release()</span> для данного типа объектов. Два других поля (<span class="mono">sysfs_ops</span> и <span class="mono">default_attrs</span>) определяют, как объекты данного типа представляются в sysfs; эти поля мы рассматривать не будем, потому что их описание выходит за рамки данного документа.
</p>
<p align="justify">Указатель <span class="mono">default_attrs</span> указывает на список атрибутов по умолчанию, которые будут создавать для любого объекта данного типа.
</p>
<h4>kset</h4>
<p align="justify">kset - это, в общем-то, множество объектов, которые должны быть ассоциированы друг с другом. Объекты, принадлежащие одному множеству, не обязаны быть одного типа. Но когда множество гетерогенно, будьте осторожны.
</p>
<p align="justify">Множества выполняют следующие функции:
<ul>
<li>Множество - это своеобразный мешок, куда складываются объекты. Оно может использоваться для отслеживания "всех блочных устройств" или "всех драйверов PCI-устройств"."</li>
<li>Множество - это также поддиректория в sysfs, где отображаются объекты, ассоциированные с данным множеством. Каждое множество содержит объект, который может быть родительским объектом для других объектов; в иерархии sysfs директории верхнего уровня устроены именно так.</li>
<li>Множества могут поддерживать "горячее подключение" ("hotplugging") объектов и влиять на то, как генерируются события uevent для юзерспейса.</li>
</ul>
</p>
<p align="justify">С точки зрения объектно-ориентированной парадигмы множество "kset" это класс-контейнер верхнего уровня; множество имеет собственный kobject, который, однако, управляется подсистемой управления множествами и не должен напрямую манипулироваться другими пользователями.
</p>
<p align="justify">Объекты, принадлежащие множеству, организованы в обычный связный список ядра. Объекты указывают на содержащее их множество с помощью своего поля <span class="mono">kset</span>. Почти во всех случаях объекты, принадлежащие данному множеству, указывают на соответствующий kset, как на своего родителя (точнее, внедрённый kobject данного множества kset).
</p>
<p align="justify">Поскольку множество имеет свой внедрённый объект, оно всегда должно создаваться динамически, а не объявляться статически или размещаться на стеке. Чтобы создать новое множество, используйте следующие функции:
<pre><span class="keyword"> struct</span> kset<span class="operator"> *</span>kset_create_and_add<span class="operator">(</span><span class="keyword">const</span><span class="type"> char</span><span class="operator"> *</span>name<span class="operator">,</span><span class="keyword">
struct</span> kset_uevent_ops<span class="operator"> *</span>u<span class="operator">,</span><span class="keyword">
struct</span> kobject<span class="operator"> *</span>parent<span class="operator">);</span></pre>
</p>
<p align="justify">Когда множество вам уже больше не нужно, вызовите:
<pre><span class="type"> void</span> kset_unregister<span class="operator">(</span><span class="keyword">struct</span> kset<span class="operator"> *</span>kset<span class="operator">);</span></pre>
чтобы уничтожить его. Таким образом множество будет удалено из sysfs, а его счётчик ссылок будет уменьшен (<i>ну, вероятно множество исчезнет из sysfs только когда его счётчик ссылок дропнется до нуля</i> - перев.) Когда счётчик ссылок обнулится, множество будет уничтожено. Т.к. могут существовать другие ссылки на данное множество, то вполне возможно, что множество будет уничтожено после того, как <span class="mono">kset_unregister()</span> возвратит управление.
</p>
<p align="justify">Пример использования множества можно найти в файле samples/kobject/kset-example.c дерева исходного кода ядра.
</p>
<p align="justify">Если множество должно управлять генерацией событий uevent, соответствующие операции можно определить в структуре <span class="mono">struct kset_uevent_ops</span>:
<pre><span class="keyword"> struct</span> kset_uevent_ops<span class="operator"> {</span><span class="type">
int</span><span class="operator"> (*</span>filter<span class="operator">)(</span><span class="keyword">struct</span> kset<span class="operator"> *</span>kset<span class="operator">,</span><span class="keyword"> struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">);</span><span class="keyword">
const</span><span class="type"> char</span><span class="operator"> *(*</span>name<span class="operator">)(</span><span class="keyword">struct</span> kset<span class="operator"> *</span>kset<span class="operator">,</span><span class="keyword"> struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">);</span><span class="type">
int</span><span class="operator"> (*</span>uevent<span class="operator">)(</span><span class="keyword">struct</span> kset<span class="operator"> *</span>kset<span class="operator">,</span><span class="keyword"> struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword">
struct</span> kobj_uevent_env<span class="operator"> *</span>env<span class="operator">);
};</span></pre>
</p>
<p align="justify">Функция <span class="mono">filter</span> позволяет множеству отфильтровать события, посылаемые юзерспейсу, для некоторого объекта. Если указатель равен 0, события не будут генерироваться.
</p>
<p align="justify">Функция <span class="mono">name</span> будет вызывана для переопределения имени множества, которое посылает событие. По умолчанию сохраняется имя множетва-источника события, но с помощью данной функции это имя можно изменить.
</p>
<p align="justify"><span class="mono">uevent</span> вызывается, когда событие готово к отправке в юзерспейс и позволяет передать дополнительные переменные окружения в событии uevent.
</p>
<p align="justify">Может возникнуть вопрос, а как, собственно, добавить в множество объекты, если нет специальной функции, которая бы это делала? (<i>но мы-то с вами уже знаем ответ</i> - перев.) Ответ таков: это делает функция <span class="mono">kobject_add()</span>. Когда мы передаём <span class="mono">kobject_add()</span> объект, его поле <span class="mono">kset</span> должно указывать на то множество, в которое этот объект должен быть включён. Остальную работу выполняет сама <span class="mono">kobject_add()</span>.
</p>
<p align="justify">Если родительский объект для объекта, принадлежащего данному множеству, не установлен, то он будет добавлен в директорию множества. Не все объекты множества обязательно "сидят" в директории, закреплённой за данным множеством. Если перед добавлением объекта в множество его указатель на родителя определён явным образом, объект регистрируется во множестве, но в файловой иерархии он появится в директории объекта-родителя.
</p>
<h4>Удаление объектов</h4>
<p align="justify">После того, как подсистема управления объектами успешно зарегистрировала объект, он должен быть удалён, когда в нём больше нет нужды. Чтобы сделать это, используйте <span class="mono">kobject_put()</span>. Этот вызов автоматически освободит ресурсы, занимаемые объектом. Если при регистрации объекта генерируется событие <span class="mono">KOBJ_ADD</span>, то при его удалении - <span class="mono">KOBJ_REMOVE</span>. Попутно будет выполнена необходимая работа на уровне sysfs.
</p>
<p align="justify">В случае, если вы хотите удалить объект в два шага (например, ваш код не может спать во время уничтожения объекта), воспользуйтесь вызовом <span class="mono">kobject_del()</span>, который разрегистрирует объект в sysfs. Сам объект становится "невидимым", но он продолжает существовать и число ссылок на него остаётся неизменным. Позже вызовите <span class="mono">kobject_put()</span>, чтобы освободить память, выделенную под объект.
</p>
<p align="justify"><span class="mono">kobject_del()</span> может быть использована, чтобы убрать ссылку на родительский объект, если созданы циркулярные ссылки. В некоторых случаях это нормальное явление, что родительский объект ссылается на дочерний. Циркулярные ссылки <u>должны</u> устраняться явным вызовом функции <span class="mono">kobject_del()</span> так, что будут вызваны деструкторы и объекты, которые были циклически связаны, освободят друг друга.
</p>
<h4>Где взять код примеров</h4>
<p align="justify">Чтобы иметь более полные примеры корректного использования объектов и можеств, изучите файлы samples/kobject/{kobject-example.c,kset-example.c}, которые будут скомпонованы как загружаемые модули ядра, если при конфигурировании ядра была активирована опция <span class="mono">CONFIG_SAMPLE_KOBJECT</span>.</p>
<h4>Aftermath</h4>
<p align="justify">Мы быстро пробежались по API объектов, вкратце ознакомились с устройством самих объектов и связанных с ними структур. Пришло время спросить, а зачем, собственно, всё это нужно? На самом деле, в тексте К-Х уже есть частичный ответ. Там упоминались события uevent. Хотя бы вот для этого. Но обо всё по порядку.</p>
<p align="justify">Как мы увидели, объекты предоставляют средство подсчёта ссылок, они внедряются в тот объект, которым управляют. У объектов одного типа - один деструктор. Объекты могут собираться во множества (или наборы, коллекции - как угодно). Что, помимо учёта ссылок и универсальных деструкторов дают нам объекты? Прежде всего, это структуризация. Если мы рассмотрим некое реальное устройство, то легко увидеть, что жёсткий диск подсоединён к порту SATA, порт SATA сидит на SATA-шине, SATA-шина управляется SATA-контроллером... Иерархия. Объекты позволяют нам выстроить иерархию устройств, подсоединённых к системе. Объекты ядра тесно взаимосвязаны с псевдо-ФС sysfs. Вся та лапша, которую можно увидеть под /sys - это отображения картины подключения и взаимосвязей устройств в системе, как её видит ядро. На самом верхнем уровне в /sys мы можем найти такие директории, как class, devices, bus, power, например. Если вы сталкивались с программированием драйверов для OS X, то имеете преставление об IORegistry и планах устройств. Все устройства в системе могут быть классифицированы по разным признакам. Например, является устройство блочным или символьным, каковы его потребности в питании и откуда оно это питание берёт, на какой шине сидит устройство. На самом деле, директории верхнего уровня в /sys в известной степени выполняют ту же задачу, что планы IORegistry и поэтому одно устройство может быть представлено во многих директориях. Например, тот же диск у нас будет в devices/, а симлинк на него будет и в bus/ и в block/. Кроме того, sysfs экспортирует также атрибуты устройств. Эти атрубуты являются атрибутами объектов и раз уж мы затронули эту тему, то наверно пробежимся по ней быстро. На основе текста, с сокращениями и некоторыми вольностями взятого отсюда: <a href="http://lwn.net/Articles/54651/">http://lwn.net/Articles/54651/</a></p>
<h4>Как объекты получают своё воплощение в sysfs</h4>
<p align="justify">Как мы видели ранее, при использовании <span class="mono">kobject_init()</span> мы получим самостоятельный объект, не представленный в sysfs. Если же воспользоваться API <span class="mono">kobject_register()</span> (или <span class="mono">kobject_add()</span>), в sysfs будет создана соответствующая этому объекту директория; других усилий со стороны программиста не требуется.</p>
<p align="justify">Именем директории будет имя объекта. Место директории в структуре sysfs определяется его позицией в иерархии объектов. Короче, директория объекта будет находиться в директории, редставляющей родительский объект данного объекта, т.е., того объекта, на который указывает поле parent. Если поле parent вашего объекта указывает на kset, то родителем этого объекта станет объект соответствующего множества kset. Если указатель parent не указфвает ни на kset, ни на другой объект, то директория объекта окажется в sysfs на самом верхнем уровне.</p>
<h4>Заполнение директории объекта</h4>
<p align="justify">Получить представление объекта в sysfs легко, как мы убедились. Однако, эта директория будет пустой и пользы от неё будет не много. Для связи объекта ядра с пользовательским окружением было бы лучше, если директория содержит какие-то атрибуты.Создание атрибутов требует дополнительных шагов, но в целом, это несложно.</p>
<p align="justify">Основные атрибуты в sysfs - это атрибуты по умолчанию, которые описываются в типе объекта <span class="mono">kobj_type</span>. Эти атрибуты присущи каждому объекту данного типа (см. <span class="mono">default_attrs</span> выше). Поле <span class="mono">default_attrs</span> - это указатель на массив указателей атрибутов со следующей структурой:
<pre><span class="keyword"> struct</span> attribute<span class="operator"> {</span><span class="type">
char</span><span class="operator"> *</span>name<span class="operator">;</span><span class="keyword">
struct</span> module<span class="operator"> *</span>owner<span class="operator">;</span>
mode_t mode<span class="operator">;
};</span></pre>
</p>
<p align="justify"><span class="mono">name</span> - это, собственно, имя атрибута (имя файла в sysfs), <span class="mono">owner</span> - указатель на модуль ядра (если применимо), который непосредственно имеет дело с этими атрибутами и, наконец, <span class="mono">mode</span> - режим доступа к файлу (обычная триада rwx-битов для владельца, группы и остальных). Как правило, режим - <span class="mono">S_IRUGO</span> для read-only атрибутов; если атрибут изменяемый (для пользователя из юзерспейса), то режим может быть <span class="mono">S_IWUSR</span>, что даст суперпользователю доступ к файлу в режиме записи. Последним элементом в массиве <span class="mono">default_attrs</span> должен быть NULL.</p>
<p align="justify">Поле <span class="mono">default_attrs</span> указывает на то, какие атрибуты есть в принципе, но они ещё никак не реализованы на уровне sysfs. Поле <span class="mono">kobj_type->sysfs_ops</span> определяет, как атрибуты отображаются или считываются:
<pre><span class="keyword"> struct</span> sysfs_ops<span class="operator"> {</span>
ssize_t<span class="operator"> (*</span>show<span class="operator">)(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> struct</span> attribute<span class="operator"> *</span>attr<span class="operator">,</span><span class="type">
char</span><span class="operator"> *</span>buffer<span class="operator">);</span>
ssize_t<span class="operator"> (*</span>store<span class="operator">)(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> struct</span> attribute<span class="operator"> *</span>attr<span class="operator">,</span><span class="keyword">
const</span><span class="type"> char</span><span class="operator"> *</span>buffer<span class="operator">,</span> size_t size<span class="operator">);
};</span></pre>
</p>
<p align="justify">Эти функции будут вызываться дл каждой операции чтения или записи соответственно для каждого объекта данного типа. В каждлом случае kobj - это указатель на тот объект, чьи атрибуты изменяются или читаются, <span class="mono">attr</span> - указатель на переменную типа <span class="mono">struct attribute</span>, описывающую атрибут и буфер размером в страницу для данных атрибута.</p>
<p align="justify">Задача функции show() закодировать полное значение атрибута, при этом не выходя за пределы <span class="mono">PAGE_SIZE</span>. Имейте в виду, что по соглашению атрибут sysfs содержит только одно значение или, по крайней мере, массив значений одного типа, так что ограничени на размер буфера не должно быть проблемой. Возвращаемое значение - это число байт, записанных в буфер или -1 в случае ошибки. Рассматривать <span class="mono">store()</span> нет особого смысла, суть аргументов та же, только обратное направление (чтение из буфера, а не запись в буфер) и дополнительный аргумент size</span>, определяющий длину входных данных.</p>
<p align="justify">Итак, для работы с одним выбранным атрибутом данного типа kobj_type нужна своя пара функций show() и store().</p>
<h4>Собственные атрибуты объектов</h4>
<p align="justify">Часто <span class="mono">default_attrs</span> в <span class="mono">kobj_type</span> исчерпывает собой все атрибуты, которые имеет объект. Но это вовсе не обязательно всегда так; атрибуты объекта могут быть добавлены или удалены по желанию. Если вам нужно задать новый атрибудт объекта, просто заполните структуру, описывающую атрибут, и передаёте указатель на неё следующему вызову:
<pre><span class="type"> int</span> sysfs_create_file<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> struct</span> attribute<span class="operator"> *</span>attr<span class="operator">);</span></pre>
</p>
<p align="justify">Если всё прошло гладко, будет создан файл см именем, заданным соответствующим полем структуры <span class="mono">struct attribute</span> и функция возвратит 0; в противном случае код возврата будет меньше нуля.</p>
<p align="justify">Как это ни удивительно, но удаляется атрибут таким вызовом:
<pre><span class="type"> int</span> sysfs_remove_file<span class="operator">(</span><span class="keyword">struct</span> kobject<span class="operator"> *</span>kobj<span class="operator">,</span><span class="keyword"> struct</span> attribute<span class="operator"> *</span>attr<span class="operator">);</span></pre>
</p>
<p align="justify">Кроме того, атрибуты могут связываться симлинками, они могут быть бинарными. Но эти детали мы опустим, потому что и так уже увлеклись. Суть должна быть уже ясна. Что дальше? Чтобы понять, что дальше, попробуем ответить на вопрос, а как, собственно, пользовательское окружение узнаёт о том, что к сиетеме подключено то или иное устройство? Когда-то, когда деревья были большими, а компьютеры - большими и глупыми в Linux были статичные файлы устройств. О недостатках этого способа работы с железом говорить не будем. Статический /dev сменился динамической файловой системой devfs и демоном devfsd, который заведовал всем этим хозяйством - такой подход был принят в FreeBSD. Ещё позже на смену devfsd пришёл udev (о нём я тоже писал, точнее, переводил :)) Не будем углубляться в то, как был устроен hotplug раньше и вообще, опустим все эти исторические детали. Сейчас udev - это хитрый демон, работающий в режиме пользователя, и слушающий netlink-сокет. Когда в ядре регистрируется или удаляется объект, ядро посылает uevent сообщение в сокет (вспомнили <span class="mono">kobject_uevent()</span>?). udev проделывает всю нужную работу по созданию спецфайла в /dev, подгрузке модуля, если необходимо, и созданию ополнительных симлинков. Таким образом, объекты - основа работы хотплага. Но не только его. На этапе, когда ядро уже загрузилось, но udev ещё не запустился, надо сделать так, чтобы и подключенные ранее устройства были учтены, однако посылать сообщения в netlink-сокет нет смысла. Его никто не слушает. Но вот смонтировать и заполнить sysfs мы вполне можем безо всякого udev. Когда udev будет запущен, он сможет пройтись по иерархии sysfs и создать файлы устройств. Вот зачем нам нужна sysfs и атрибуты. В частности. В общем, это нечто более глубокое, чем просто механизм поддержки хотплага. Реализация объектной модели драйверов в Linux делает возможным лёгкую реализацию хотплага, но суть именно в отношениях различных классов устройств, шин, портов и всего такого. Однако, это уже другая и определённо долгая история. Нашей целью было лишь слегка потыкать палочкой в объекты, а не заниматься всеобъемлющим описанием их анатомии, физиологии и экологии. Возможно, как-нибудь уделим внимание и этому.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-33703679896116535812014-08-12T13:52:00.000-07:002014-08-12T13:52:36.507-07:00crossdev на funtoo<div align="right"><i>Ждём ебилдов</i></div><br><br>
<p align="justify">Я уже очень давно не писал о вещах насущных и приземлённых и наверно не стал бы этого делать впредь, если бы не одно происшествие. Небольшая предыстория. Просто чтобы как-то занять место. Когда-то я пользовался ubuntu, но потом перестал и перешёл на gentoo. Сей дистрибутив пришёлся мне зело по нраву. Нет, дело отнюдь не в мифических процентах производительности. Просто gentoo позволяет вертеть пакетами по своему желанию и усмотрению в довольно широких пределах. Чуть позже передо мной встал вопрос, на что заменить уже на другой системе старый, заваленный всяким хламом Debian, который было проще убить, чем пытаться дальше шаманить с обновлениями (и не зря, как убедительно показали страсти по systemd какое-то время спустя - выхаживать Debian ради того, чтобы однажды к тебе приехало вышеупомянутое чудо поттерингомысли...) Кое-кто из знакомых пользовался форком gentoo - <a href="http://www.funtoo.org/Welcome">funtoo</a>, ну и не то мне посоветовали попробовать, не то самому стало интересно, в общем, я решился на эксперимент. Тем более, что это практически та же gentoo, только в профиль и с git'ом вместо rsync'а для обновлений, никаких проблем возникнуть не должно было. Так в целом и получилось. Дела шли неплохо, пока однажды я подумал, а не поставить ли мне mingw-w64 там, как и на gentoo? Всё удобнее, чем всякую мелочь при необходимости собирать на соседнем хосте. Вытянул я требуемый для сего crossdev, повелеваю системе
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>crossdev -S -t i686-w64-mingw32</pre></span>
И продолжаю заниматься своими делами, благо не близкий свет - пока всё соберётся. Что поделаешь, плата за гибкость.</p>
<p align="justify">И тут началось... Дело в том, что crossdev берёт на себя весь труд утащить из интернетов всё, что нужно и расквартировать это у вас на диске. Всё, что нужно, включает binutils, mingw64-runtime конкретно для таргета i686-w64-mingw32, собственно gcc. binutils собирается без проблем, gcc тоже... Кажется. mingw64-runtime стоит колом, хоть ты тресни. Давайте разбираться. По умолчанию crossdev приволочёт вам последний стабильный ебилд, в моём случае, это был gcc-4.8.2, в то время, как хост пользуется gcc-4.7.3, 4.8 для хоста я замаскировал. Честно признаться, не вдаваясь особо в детали, я сначала подумал было, что косяк с версией. На gentoo последний ебилд из серии >4.8.2 категорически не собирается под i686-w64-mingw32, мотивируя этом неразрешёнными символами. Ну что ж, я вполне счастлив и с 4.8.2. Там я просто замаскировал до поры до времени то, что не собирается, просто чтобы не тратить время на пустые попытки и не стопорить прочие обновления. Здесь решил поступить так же, с той лишь разницей, что под маской должны были скрыться все компиляторы >4.8, т.о., ко мне должен был приехать компилятор той же версии, что у хоста - 4.7.3-r2. В общем, командую:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>crossdev -S -t i686-w64-mingw32 --g 4.7.3-r2</pre></span>
и дальше по своим делам.</p>
<p align="justify">Итог - file collision. То, что должно быть установлено этим ебилдом, каким-то образом уже есть в системе. Забавная петрушка. Но и правда, emerge пытается скопировать файлы с именем i686-pc-linux-*-4.7.3, при том, что такие файлы уже есть в /usr и совершенно внезапно принадлежат они моему законному хост-компилятору. Коллизия вполне очевидная. И что ещё смешнее, будь это уже упомянутый gcc-4.8.2, у вас и окажется i686-pc-linux-*-4.8.2! А вот i686-w64-mingw32-*-4.8.2 вы ни в жисть не увидите. И в этом вы будете не одиноки. Скрипт конфигурирования mingw64-runtime тоже его не увидит, потому что его нет.</p>
<p align="justify">Улавливаете логику? Ваш crossdev становится лазейкой для контрабанды замаскированных версий gcc! Вместо того, чтобы собрать и поставить в систему компилятор под таргет i686-w64-mingw32, он соберёт вам ещё один компилятор для того же таргета, что и ваш хост (CHOST = CTARGET, unconditionally) и поставит его рядом, не забыв сделать компилятором по умолчанию. <a href="https://bugs.funtoo.org/browse/FL-821">Вот это проливает свет</a> на причину. А вот <a href="https://www.linux.org.ru/forum/development/9678334">здесь</a> немножко обсуждения, самхау рилейтед. Похоже, местный Патрегбох нещадно поломал ебилды компиляторов. Вот, собственно, и всё недолга. Все мы люди - хотелось как лучше, а получилось не очень. Фикс есть, хотя достаточно костыльный, но действенный. Можно утащить ебилд gcc из gentoo, иных путей, кроме тотального переписывания ебилда или отката коммитов я лично не увидел. Но первое слишком муторно, а второе уже не в нашей власти, если говорить о глобальных изменениях. Хотя crossdev собирает инструментарий для кросскомпиляции в оверлее, есть одна хитрость, ебилды он берёт системные, что почему-то особого удивления и не вызывает, так что можно утащить ебилд откуда-нибудь <a href="http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/sys-devel/gcc/">отсюда</a> или из /usr/portage/sys-devel/gcc на работающей gentoo-системе и кинуть его в аналогичное место на funtoo. Есть подводный камень, если ебилд, устанавливаемый crossdev'ом имеет ту же версию, что ваш хост-компилятор. С вероятностью, равной единице, ваш хост-компилятор очень захочет пересобраться при следующей проверке обновлений системы (emerge -uDNpv world) из-за разницы в флагах. Поэтому стратегически выгоднее ставить версию компилятора где-то рядом с вашей, а не такую же. Например, если у вас хост-компилятор gcc-4.7.3-r2, то попросите crossdev поставить gcc-4.7.3-r1. Топорный путь, но что поделаешь. Зато быстрый и работает. В общем, ждём-с.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com1tag:blogger.com,1999:blog-3876463594612412036.post-9838823660574751902014-06-09T03:41:00.002-07:002014-08-12T22:21:04.277-07:00Пользовательские привилегии в Linux<style type="text/css">.comment { color: #999999; font-style: italic; }
.pre { color: #000099; }
.string { color: #009900; }
.char { color: #009900; }
.float { color: #996600; }
.int { color: #999900; }
.bool { color: #000000; font-weight: bold; }
.type { color: #FF6633; }
.flow { color: #FF0000; }
.keyword { color: #990000; }
.operator { color: #663300; font-weight: bold; }
.operator { color: #663300; font-weight: bold; }</style>
<p align="justify">В свете уже поднятой и рассмотренной <a href="http://rflinux.blogspot.com/2014/06/linux-caps.html">здесь</a> темы встаёт закономерный вопрос: всё это конечно хорошо и замечательно, но как можно наделить конкретными привилегиями не отдельный процесс, а пользователя, чтобы можно было создавать своих root'ов и при этом ограничить множество доступных пользователю привилегий? Ответ на этот вопрос - Pluggable Authentication Modules, также известные, как подключаемые модули аутентификации или попросту PAM, а именно, модуль pam_cap.so. Вместо того, чтобы ассоциировать разрешения с конкретными файлами, мы могли бы наделить конкретного пользователя нужной привилегией. При успешной авторизации шелл пользователя получит необходимое разрешение в множество наследуемых разрешений, которое будет унаследовано всеми его дочерними процессами, а это значит всеми процессами данного пользователя.</p>
<p align="justify">Сперва добавим модуль авторизации:</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># cat /etc/pam.d/system-login
auth required pam_cap.so</pre></span>
<p align="justify">Теперь определим, что и кому мы разрешаем:</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># cat /etc/security/capability.conf
cap_kill,cap_setpcap luser
none *</pre></span>
<p align="justify">Пользователь luser получает право на привилегии cap_kill - обход проверок безопасности при посылке сигналов другим процессам, не принадлежащим данному пользователю, и разрешение на манипуляции привилегиями. По умолчанию модуль pam_cap.so обычно не подключается, но если бы подключался, то обычный "бесправный" пользователь описывался бы строкой "none *".</p>
Итак, luser получает свои разрешения cap_kill и cap_setpcap, а все остальные не будут иметь никаких разрешённых привилегий. Кстати, особенность состоит в том, что присваиваемые таким образом привилегии <i>не являются активными</i>. Они лишь наследуются и могут быть разрешены - далее мы увидим, как, а пользовательский процесс, требующий, допустим, привилегию cap_net_raw, сможет её активировать. Изначально же она будет присутствовать лишь во множестве наследуемых (inheritable) привилегий. Чем-то это напоминает Windows, где пользователи из группы администраторов могут иметь в токене безопасности привилегию отладки процесса (т.е., практически полного доступа к нему, включая операции над содержимым его памяти), что ещё не означает автоматически возможность каждого администраторского процесса воспользоваться ею, ведь её ещё надо активировать (что обычно происходит после принятия консента на системах с UAC - т.е. всей линейки Vista+). Однако здесь у нас всё чуть-чуть сложнее.</p>
<p align="justify">Проверим, что у нас получилось:</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ grep ^Cap /proc/$$/status
CapInh: 0000000000000120
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000001fffffffff
$ capsh --decode=0000000000000120
0x0000000000000120=cap_kill,cap_setpcap</pre></span>
<p align="justify">Вроде бы всё работает. Следует заметить, что само по себе это ещё мало что значит, ведь разрешённые привилегии только лишь наследуются, но они даже не входят в множество доступных привилегий (CapPrm). Рассмотрим небольшой самодельный пример:
<pre><span class="pre">#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <unistd.h>
</span><span class="type">
int</span> cap_enable<span class="operator"> (</span>cap_value_t cap<span class="operator">)
{</span>
cap_t _caps<span class="operator">;</span>
_caps<span class="operator"> =</span> cap_get_proc<span class="operator"> ();</span><span class="flow">
if</span><span class="operator"> (</span>_caps<span class="operator"> ==</span> NULL<span class="operator">)</span><span class="flow">
return</span><span class="operator"> -</span><span class="int">1</span><span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>cap_set_flag<span class="operator"> (</span>_caps<span class="operator">,</span> CAP_EFFECTIVE<span class="operator">,</span><span class="int"> 1</span><span class="operator">, &</span>cap<span class="operator">,</span> CAP_SET<span class="operator">) !=</span><span class="int"> 0</span><span class="operator">)</span><span class="flow">
return</span><span class="operator"> -</span><span class="int">1</span><span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>cap_set_proc<span class="operator"> (</span>_caps<span class="operator">) !=</span><span class="int"> 0</span><span class="operator">)</span><span class="flow">
return</span><span class="operator"> -</span><span class="int">1</span><span class="operator">;</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;
}</span><span class="type">
void</span> dump_caps<span class="operator"> (</span><span class="keyword">const</span><span class="type"> char</span><span class="operator">*</span> img_name<span class="operator">,</span><span class="keyword"> const</span><span class="type"> char</span><span class="operator">*</span> m<span class="operator">)
{</span><span class="flow">
if</span><span class="operator"> (</span>m<span class="operator">)</span>
printf<span class="operator"> (</span>m<span class="operator">);</span>
printf<span class="operator"> (</span><span class="string">" file caps %s\n"</span><span class="operator">,</span> cap_to_text<span class="operator"> (</span>cap_get_file<span class="operator"> (</span>img_name<span class="operator">),</span> NULL<span class="operator">));</span>
printf<span class="operator"> (</span><span class="string">" process caps %s\n"</span><span class="operator">,</span> cap_to_text<span class="operator"> (</span>cap_get_proc<span class="operator"> (),</span> NULL<span class="operator">));</span><span class="flow">
return</span><span class="operator">;
}</span><span class="type">
int</span><span class="keyword"> main</span><span class="operator">(</span><span class="type">int</span> argc<span class="operator">,</span><span class="type"> char</span><span class="operator"> **</span> argv<span class="operator">)
{</span>
dump_caps<span class="operator"> (</span>argv<span class="operator"> [</span><span class="int">0</span><span class="operator">],</span><span class="string"> "before enabling...\n"</span><span class="operator">);</span>
sleep<span class="operator"> (</span><span class="int">30</span><span class="operator">);</span>
cap_enable<span class="operator"> (</span>CAP_KILL<span class="operator">);</span>
dump_caps<span class="operator"> (</span>argv<span class="operator"> [</span><span class="int">0</span><span class="operator">],</span><span class="string"> "after enabling...\n"</span><span class="operator">);</span>
sleep<span class="operator"> (</span><span class="int">60</span><span class="operator">);
}</span></pre>
Его сборка и выполнение должны дать такие результаты:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ ./cap_test
before enabling...
file caps (null)
process caps = cap_kill,cap_setpcap+i
after enabling...
file caps (null)
process caps = cap_kill,cap_setpcap+i</pre></span>
То есть, привилегии недоступны. Чтобы понять, в чём дело, вспомним уже упоминавшиеся формулы, по которым вычисляются разрешения:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset)
P'(effective) = F(effective) ? P'(permitted) : 0
P'(inheritable) = P(inheritable)</pre></span>
где F - разрешения, ассоциированные с файлом, а P - с процессом. Штрихом обозначены разрешения после exec(). Иными словами, для того, чтобы наша программа получила привилегию в свой набор доступных разрешений, необходимо либо чтобы для файла было установлено наследование сообветствующей привилегии, либо, чтобы она изначально входила в множество доступных привилегий файла. Т.е., либо cap_kill+i, либо cap_kill+p. Остановимся на первом варианте:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># setcap cap_kill+i ./cap_test
$ ./cap_test
before enabling...
file caps = cap_kill+i
process caps = cap_kill+ip cap_setpcap+i
after enabling...
file caps = cap_kill+i
process caps = cap_kill+eip cap_setpcap+i
[ в другом терминале между паузами ]
$ grep ^Cap /proc/`pgrep cap_test`/status
CapInh: 0000000000000120
CapPrm: 0000000000000020
CapEff: 0000000000000000
CapBnd: 0000001fffffffff
$ grep ^Cap /proc/`pgrep cap_test`/status
CapInh: 0000000000000120
CapPrm: 0000000000000020
CapEff: 0000000000000020
CapBnd: 0000001fffffffff</pre></span>
На первый взгляд ненамного удобнее. Более того, мы всё ещё зависим от разрешений, ассоциированных с файлами. Однако именно в этом и состоит гибкость. Во-первых, при распределении привилегий среди пользователей, мы наделяем их лишь некоторыми полномочиями. И именно пользователей. Кто-то сможет посылать сигналы чужим процессам, а кто-то нет. Во-вторых, с помощью дисковых разрешений, мы определяем, что пользователям можно запускать с данными привилегиями, а что нет, потому что позволять пользователю запускать вообще всё, что попало при данном повышенном уровне привилегий - это тоже не лучшее решение. Так что наделив пользователя небольшим кусочком власти, мы всё ещё контролируем, как он сможет этой властью воспользоваться. Однако приведённый пример имеет одну небольшую особенность. Программа в курсе относительно привилегий и сама изменяет свои разрешения на основе заданных значений. Для того, чтобы применять привилегии к любым программам, в том числе таким, которые могут ничего не знать о привилегиях, необходимо снова использовать форсированные разрешения, которые ассоциированы с файлами на диске. Например, так setcap cap_kill+ei ./cap_test (при логине пользователь получает унаследованное разрешение cap_kill, для файла cap_test cap_kill тоже помечено, как наследуемое разрешение - в соответствие с правилом, приведённом выше, при запуске cap_test разрешение cap_kill будет занесено в его множество доступных привилегий - P'(permitted) = P(inheritable) & F(inheritable); если для данного файла данное разрешение входит в множество эффективных разрешений, то оно заносится и в множество эффективных, или действительных, разрешений выполняемого процесса: P'(effective) = F(effective) ? P'(permitted) : 0). Допустим, что наш cap_test имеет такие разрешения:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ getcap ./cap_test
./cap_test = cap_kill,cap_net_admin+ei</pre></span>
Однако, <i>процесс</i> не получит привилегию cap_net_admin в какое-либо из своих множеств, если эта привилегия не была предоставлена пользователю через PAM:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ ./cap_test
before enabling...
file caps = cap_kill,cap_net_admin+ei
process caps = cap_kill+eip cap_setpcap+i
after enabling...
file caps = cap_kill,cap_net_admin+ei
process caps = cap_kill+eip cap_setpcap+i</pre></span>
Таким образом, это скорее механизм ограничения и разграничения привилегий, чем наделения ими :) На первый взгляд всё несколько запутанно, но на деле, привилегии не так страшны. Напоследок, если вдруг по какой-то причине вы не можете воспользоваться библиотекой libcap, активировать привилегию можно вот так, напрямую используя сисколлы (не совсем напрямую, ибо libc, но уже без посредничества libcap):
<pre><span class="pre">#include <sys/capability.h>
</span><span class="type">
int</span><span class="keyword"> main</span><span class="operator"> (</span><span class="type">int</span> argc<span class="operator">,</span><span class="type"> char</span><span class="operator"> **</span>argv<span class="operator">)
{</span><span class="keyword">
struct</span> __user_cap_header_struct hdr<span class="operator">;</span><span class="keyword">
struct</span> __user_cap_data_struct data<span class="operator">;</span>
memset<span class="operator"> (&</span>hdr<span class="operator">,</span><span class="int"> 0</span><span class="operator">,</span><span class="keyword"> sizeof</span><span class="operator">(</span>hdr<span class="operator">));</span>
hdr<span class="operator">.</span>version<span class="operator"> =</span> _LINUX_CAPABILITY_VERSION<span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>capget<span class="operator"> (&</span>hdr<span class="operator">, &</span>data<span class="operator">) <</span><span class="int"> 0</span><span class="operator">)</span><span class="flow">
return</span><span class="int"> 1</span><span class="operator">;</span>
data<span class="operator">.</span>effective<span class="operator"> |=</span> CAP_TO_MASK<span class="operator">(</span>CAP_KILL<span class="operator">);</span>
data<span class="operator">.</span>effective<span class="operator"> |=</span> CAP_TO_MASK<span class="operator">(</span>CAP_SETPCAP<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>capset<span class="operator"> (&</span>hdr<span class="operator">, &</span>data<span class="operator">) <</span><span class="int"> 0</span><span class="operator">)</span><span class="flow">
return</span><span class="int"> 2</span><span class="operator">;</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;
}</span></pre>
Но разумеется, лучше пользоваться libcap, как более портабельным API.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-27112383970813134632014-06-08T05:13:00.001-07:002014-11-24T22:13:40.604-08:00Разрешения процессов в Linux<style type="text/css">.source { display: inline-block; width: 8; }
.comment { color: #999999; font-style: italic; }
.pre { color: #000099; }
.string { color: #009900; }
.char { color: #009900; }
.float { color: #996600; }
.int { color: #999900; }
.bool { color: #000000; font-weight: bold; }
.type { color: #FF6633; }
.flow { color: #FF0000; }
.keyword { color: #990000; }
.operator { color: #663300; font-weight: bold; }
.operator { color: #663300; font-weight: bold; }
.mono { font-family: "Courier New", Courier, monospace; }
td { border-top: 1px solid black; border-collapse: collapse; }</style><h4>Терминологический казус</h4><p align="justify">То, о чём сейчас пойдёт речь, по-английски называется capabilities. Дословно "возможности", "способности". Семантически, это то, что называется привилегиями. Как ни странно, хоть тема и не нова, устоявшегося русского перевода этого термина я не видел. Точнее даже так, я никакого русского перевода не видел. И этот момент несколько смущает. Но ничего не поделаешь, придётся выкручиваться своими силами. Мне кажется, что наиболее адекватный перевод в данном случае - "разрешения", хотя по сути речь как раз о возможностях. Но раз обсуждаемый предмет - это то, что процессу разрешено делать, а не какие-то сугубо процесс-специфические возможности, им самим и создаваемые, я счёл более корректной приведённую версию перевода: разрешения процессов или привилегии. В дальнейшем я буду применять эти два понятия, как синонимы. Ещё один казуистический момент, так как с точки зрения ядра поток и процесс описываются единым дескриптором, дескриптором задачи (thread - это аппаратный контекст, являющийся частью дескриптора задачи), я не делаю разницы между потоком и процессом, так как здесь она не имеет особого значения. Важно лишь уточнить, что разрешения являются такой же частью дескриптора задачи, как и аппаратный контекст выполнения. И раз уж речь зашла о ядре (впрочем она и дальше пойдёт во многом именно о ядре), то сразу определимся, что речь идёт о версии 3.12. Система разделения власти суперпользователя на множество привилегий появилась в доисторической с точки зрения сегодняшнего дня ветке ядра 2.2. За эти годы многое изменилось самым кардинальным образом. Поэтому даже между ветками 2.6 и 3 будут расхождения в коде. Но общие принципы остаются в целом теми же.</p><h4>Постановка задачи</h4><p align="justify">В общем и в целом, всё просто. Кто пользуется таким замечательным инструментом, как wireshark, наверняка сталкивался уже с разрешениями процессов. Суть идеи такова: есть масса различных операций, результаты которых имеют критическое значение для всей системы, или же могут сказаться на отдельных пользователях этой самой системы, если оных много. Дабы свести к минимуму риск от потенциально деструктивных операций, система не даёт их выполнять обычным пользователям. А для административных задач есть всемогущий root и подразумевается, что человек, знающий пароль root'а на данной системе знает, что делает. Всё просто и вроде бы даже эффективно. Противопоставление "root vs обычный пользователь" простое, понятное, как чёрное и белое, и прозрачное. В идеале, пользователи имеют лишь минимально необходимый набор прав, они не могут навредить друг другу, так как доступ к данным защищён правами владения и доступа, не могут нанести вред системе и всё ещё могут добровольно делиться друг с другом доступом к данным. Идиллия. Но не всё так просто. Нынче Unix-подобные ОС уже совсем не обязательно работают лишь на системах, обслуживающих уйму пользователей. И тем не менее, вхождение в массы не изменило одного обстоятельства. Сидеть под root'ом всё так же моветон и категорически не рекомендуется без настоятельной нужды. А что делать, если пользователь на своей системе хочет изменить время? И пусть даже он единственный живой пользователь системы и никому навредить не сможет, кроме самого себя, операция изменения системного времени всё ещё требует привилегий. Что же делать? Тем более, если наш пользователь, скажем, путешествует туда-сюда по всему земному шару и меняет время чуть ли не каждый день. Прикажете ему сидеть под root'ом? Есть sudo, а вместе с этим и проблема. Когда наш пользователь сделает sudo date, процесс сможет не только изменять системное время, но делать всё то, что может сделать root. А что если злобные хацкеры подменили образ date на диске, пока наш пользователь плевал вниз с Эйфелевой башни, например? Вот тут-то и появилось осознание, что чего-то не хватает. Этот пробел призваны восполнить разрешения процессов в Linux. Если кратко, то существует целый класс операций, требующих привилегий. Однако специализированной программе, устанавливающей системное время, требуется право лишь на одну такую операцию. Чтобы избежать вероятности злоупотребления привилегиями, которые получает процесс с EUID=0, мы наделим нашу программу разрешением ровно на одну операцию. Всё остальное будет запрещено и наши злобные горе-хакеры останутся ни с чем. И хотя не стоит воспринимать приведённый выше пример всерьёз, проблема гранулярности традиционной модели безопасности Unix назрела давно. Более удачный и жизненный пример всё тот же wireshark - программа с графическим интерфейсом пользователя, которая позволяет следить за сетевым трафиком. Для того, чтобы успешно справляться со своей задачей, wireshark должен иметь право на создание "сырых" сокетов (raw sockets). При этом, GUI такие полномочия совершенно ни к чему и напротив, чем меньше кода выполняется с EUID=0, тем лучше. Ещё один пример из этой же области - ping. Раньше это решалось либо установкой suid-бита либо с помощью sudo. Решения, прямо скажем, далёкие от идеала и гибкости. Проблемы, связанные с подходами вроде sudo или suid-бита, были осознаны многими и в каждом потомке Unix они решались по-своему. В Solaris, к примеру, есть RBAC - разделение привилегий на основе ролей. Хотя это не то же самое, что разрешения процессов, но с RBAC тесно связана идея привилегий. В Linux же у нас есть process capabilities. И появились они, кстати, уже довольно давно, но по разным причинам широкого применения не нашли. Кажется, ситуация начинает медленно меняться, так что разберёмся с этим механизмом получше.</p><h4>Кровавые детали</h4><p align="justify">Контроль привилегий осуществляет ядро. В ядре Linux привилегии являются ничем иным, как битовыми картами. Чтобы убедиться в этом, проследите за корнями соответствующих структур в ядре Linux (интересующие нас поля выделены жирным курсивом, дескриптор задачи - <span class="mono">task_struct</span>, здесь сильно сокращён по понятным причинам):</p><p align="justify">Начнём с <span class="mono">include/linux/sched.h:</span></p><pre><span class="keyword">struct</span> task_struct<span class="operator"> {</span><span class="keyword">
volatile</span><span class="type"> long</span> state<span class="operator">;</span><span class="comment"> /* -1 unrunnable, 0 runnable, >0 stopped */</span><span class="type">
void</span><span class="operator"> *</span>stack<span class="operator">;</span>
atomic_t usage<span class="operator">;</span><span class="type">
unsigned int</span> flags<span class="operator">;</span><span class="comment"> /* per process flags, defined below */</span><span class="type">
unsigned int</span> ptrace<span class="operator">;
...</span><span class="comment">
/* process credentials */</span><b><i><span class="keyword">
const struct</span> cred __rcu<span class="operator"> *</span>real_cred<span class="operator">;</span><span class="comment"> /* objective and real subjective task
* credentials (COW) */</span><span class="keyword">
const struct</span> cred __rcu<span class="operator"> *</span>cred<span class="operator">;</span><span class="comment"> /* effective (overridable) subjective task
* credentials (COW) */</span></i></b><span class="type">
char</span> comm<span class="operator">[</span>TASK_COMM_LEN<span class="operator">];</span><span class="comment"> /* executable name excluding path
- access with [gs]et_task_comm (which lock
it with task_lock())
- initialized normally by setup_new_exec */</span><span class="operator">
...
};</span></pre><p align="justify">Далее <span class="mono">include/linux/cred.h:</span></p><pre><span class="keyword">struct</span> cred<span class="operator"> {</span>
atomic_t usage<span class="operator">;</span><span class="pre">
#ifdef CONFIG_DEBUG_CREDENTIALS
</span> atomic_t subscribers<span class="operator">;</span><span class="comment"> /* number of processes subscribed */</span><span class="type">
void</span><span class="operator"> *</span>put_addr<span class="operator">;</span><span class="type">
unsigned</span> magic<span class="operator">;</span><span class="pre">
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
</span> kuid_t uid<span class="operator">;</span><span class="comment"> /* real UID of the task */</span>
kgid_t gid<span class="operator">;</span><span class="comment"> /* real GID of the task */</span>
kuid_t suid<span class="operator">;</span><span class="comment"> /* saved UID of the task */</span>
kgid_t sgid<span class="operator">;</span><span class="comment"> /* saved GID of the task */</span>
kuid_t euid<span class="operator">;</span><span class="comment"> /* effective UID of the task */</span>
kgid_t egid<span class="operator">;</span><span class="comment"> /* effective GID of the task */</span>
kuid_t fsuid<span class="operator">;</span><span class="comment"> /* UID for VFS ops */</span>
kgid_t fsgid<span class="operator">;</span><span class="comment"> /* GID for VFS ops */</span><span class="type">
unsigned</span> securebits<span class="operator">;</span><span class="comment"> /* SUID-less security management */</span>
<b><i>kernel_cap_t cap_inheritable<span class="operator">;</span><span class="comment"> /* caps our children can inherit */</span>
kernel_cap_t cap_permitted<span class="operator">;</span><span class="comment"> /* caps we're permitted */</span>
kernel_cap_t cap_effective<span class="operator">;</span><span class="comment"> /* caps we can actually use */</span>
kernel_cap_t cap_bset<span class="operator">;</span><span class="comment"> /* capability bounding set */</span></i></b><span class="pre">
#ifdef CONFIG_KEYS
</span><span class="type"> unsigned char</span> jit_keyring<span class="operator">;</span><span class="comment"> /* default keyring to attach requested
* keys to */</span><span class="keyword">
struct</span> key __rcu<span class="operator"> *</span>session_keyring<span class="operator">;</span><span class="comment"> /* keyring inherited over fork */</span><span class="keyword">
struct</span> key<span class="operator"> *</span>process_keyring<span class="operator">;</span><span class="comment"> /* keyring private to this process */</span><span class="keyword">
struct</span> key<span class="operator"> *</span>thread_keyring<span class="operator">;</span><span class="comment"> /* keyring private to this thread */</span><span class="keyword">
struct</span> key<span class="operator"> *</span>request_key_auth<span class="operator">;</span><span class="comment"> /* assumed request_key authority */</span><span class="pre">
#endif
#ifdef CONFIG_SECURITY
</span><span class="type"> void</span><span class="operator"> *</span>security<span class="operator">;</span><span class="comment"> /* subjective LSM security */</span><span class="pre">
#endif
</span><span class="keyword"> struct</span> user_struct<span class="operator"> *</span>user<span class="operator">;</span><span class="comment"> /* real user ID subscription */</span><span class="keyword">
struct</span> user_namespace<span class="operator"> *</span>user_ns<span class="operator">;</span><span class="comment"> /* user_ns the caps and keyrings are relative to. */</span><span class="keyword">
struct</span> group_info<span class="operator"> *</span>group_info<span class="operator">;</span><span class="comment"> /* supplementary groups for euid/fsgid */</span><span class="keyword">
struct</span> rcu_head rcu<span class="operator">;</span><span class="comment"> /* RCU deletion hook */</span><span class="operator">
};</span></pre><p align="justify">И наконец <span class="mono">include/linux/capability.h:</span></p><pre><span class="keyword"> typedef struct</span> kernel_cap_struct<span class="operator"> {</span>
__u32 cap<span class="operator">[</span>_KERNEL_CAPABILITY_U32S<span class="operator">];
}</span> kernel_cap_t<span class="operator">;</span></pre><p align="justify">Т.е., в текущей версии реализации одно множество разрешений представлено двумя 32-битными словами или 64 битами (строка "<span class="pre">#define _LINUX_CAPABILITY_U32S_3 2</span>" в файле <span class="mono">include/uapi/linux/capability.h</span>). Всего таких множеств 3 (см. выше определение структуры <span class="mono">struct cred</span>): разрешённые (permitted), эффективные (effective), наследуемые (inheritable). В общем-то названия этих множеств и краткие комментарии в коде говорят сами за себя. Множество разрешённых привилегий - это те разрешения, которые наш процесс может активировать в принципе, но которые не обязательно действуют в данный момент времени. Если процесс исключает привилегию из множества доступных (privilege drop), то он больше не может ею воспользоваться, в том числе, он не сможет вернуть себе право на данную привилегию (reacquire privilege) Множество эффективных привилегий - это те элементы множества разрешённых привилегий, которые были активированы (запрошены) процессом и действуют в данный момент. Именно это поле используется ядром для проверки права на данную операцию. И, наконец, множество наследуемых привилегий - это те разрешения, с которыми может работать дочерний процесс, порождённый нашим процессом, т.е., множество привилегий, которые присваиваются процессу, порождённому с помощью <span class="mono">exec*()</span> (все они в конечном счёте сходятся на <span class="mono">execve()</span>). Процесс, созданный в результате <span class="mono">fork()</span>, получает полные копии множеств привилегий родительского процесса. Эти три множества привилегий свойственны и выполняющимся процессам, и файлам (о файлах и привилегиях чуть ниже). Ещё одно множество - ограничивающее (bounding set). Если с 3 другими множествами всё ясно, то на этом стоит остановиться чуть подробнее. Во-первых, это множество в отличие от уже рассмотренных свойственно только процессам, но не файлам. Во-вторых, это ещё один ограничительный механизм, но в отличие от множества доступных процессу (permitted) привилегий, устанавливаемого при запуске исполняемого образа или при посредничестве другого процесса, это множество устанавливается для процессов-потомков, порождённых вызовом <span class="mono">execve()</span>. Происходит это следующим образом: во время выполнения <span class="mono">exec*()</span> к привилегиям из ограничивающего множества (bounding set) процесса, вызывающего <span class="mono">exec*()</span>, и множества доступных привилегий (permitted), ассоциированного с файлом исполняемого образа на диске, применяется логическое И, а результат заносится в множество разрешённых привилегий дочернего процесса. Таким образом, потомку будут доступны лишь те операции, которые есть и в ограничивающем множестве, и во множестве разрешённых привилегий, прочитанном с диска. Начиная с версии Linux 2.6.25 процесс не может добавить привилегию в множество наследуемых привилегий (inheritable), если она не входит в ограничивающее множество, даже не смотря на то, что данная привилегия доступна вызывающему потоку (т.е., она может быть в его permitted-множестве). Ограничивающее множество является маской разрешённых привилегий, определённых для файла, но не для множества <i>уже унаследованных</i> привилегий. Таким образом, это механизм контроля наследования разрешений, а не их использования уже существующим процессом. Следует отметить важный технический момент: операция ограничения привилегий в соответствии с маской ограничивающего множества не выполняется непосредственно над атрибутами файла, а над разрешениями свежесозданного процесса. Так что хоть ограничивающее множество действует в первую очередь на те разрешения, которые ассоциированы с файлом, вычисления происходят на множествах разрешений процесса, не файла. Создаваемый процесс получит в точности те разрешения, которые свойственны файлу исполняемого образа, но позже они будут откорректированы в соответствии с маской родительского процесса. Звучит запутанно, но надеюсь, всё станет понятнее далее, когда мы дойдём до файловых атрибутов. В зависимости от версии ядра ограничивающее множество - это либо системный атрибут, либо свойство конкретного процесса (<a href="http://lwn.net/Articles/251666/">http://lwn.net/Articles/251666/</a>). До версии 2.6.25 маска задаётся через файл /proc/sys/kernel/cap-bound в виде целого со знаком в десятичной системе. Эту маску может установить только init. Процессы c EUID=0, имеющие разрешение <span class="mono">CAP_SYS_MODULE</span>, могут <i>удалять</i> отдельные привилегии из этой маски. Обычно на таких системах маска не включает разрешение <span class="mono">CAP_SETPCAP</span>. Чтобы снять это ограничение, достаточно изменить определение <span class="mono">CAP_INIT_EFF_SET</span> в файле <span class="mono">include/linux/capability.h</span> и пересобрать ядро. Начиная с версии 2.6.25 ограничивающее множество больше не является глобальным общесистемным атрибутом. Оно наследуется при вызове <span class="mono">fork()</span> и <span class="mono">exec*()</span>. Поток может удалять привилегии из своего ограничивающего множества. Детали см. в <span class="mono">capabilities(2)</span></p>
<p align="justify">Итак, отдельно взятая привилегия - это ни много, ни мало бит в битовой карте, представляющей множество разрешённых, эффективных или наследуемых привилегий. Приведём наконец некоторые примеры конкретных привилегий:</p>
<table width="70%" style="margin:0px auto;">
<col width="25%">
<col width="70%">
<tr>
<td>CAP_SETPCAP</td>
<td>Манипуляции привилегиями и, соответственно, разрешение использовать вызов <span class="mono">capset()</span> в ядрах, где поддержка файловых разрешений не включена.<br />
Если же файловые разрешения поддерживаются, эта привилегия позволяет добавлять разрешения в ограничивающее множество потока, а также удалять их оттуда (<span class="mono">prctl(2)</span>); манипуляции с флагами безопасности (<a href="http://lwn.net/Articles/368600/">http://lwn.net/Articles/368600/</a>)</td>
</tr>
<tr>
<td>CAP_NET_ADMIN</td>
<td>Различные операции, относящиеся к сетевому администрированию, как то: конфигурирование сетевых интерфейсов, администрирование файрволла, изменение таблицы маршрутизации, включение "неразборчивого" режима на интерфейсе и тому подобное</td>
</tr>
<tr>
<td>CAP_CHOWN</td>
<td>Манипуляции с идентификаторами пользователя/группы объекта файловой системы (<span class="mono">chown(2)</span>)</td>
</tr>
<tr>
<td>CAP_DAC_OVERRIDE</td>
<td>Обход проверок безопасности при операциях ввода-вывода</td>
</tr>
<tr>
<td>CAP_KILL</td>
<td>Обход проверок безопасности при посылке сигналов (<span class="mono">kill(2)</span>)</td>
</tr>
<tr>
<td>CAP_SETFCAP (начиная с Linux 2.6.24)</td>
<td>Манипуляции с файловыми разрешениями</td>
</tr>
<tr>
<td>CAP_SYSLOG (начиная с Linux 2.6.37)</td>
<td>Привилегированные операции с системным логом; отображение адресов ядра в /proc, если атрибут /proc/sys/kernel/kptr_restrict установлен в 1
</td>
</tr>
<tr>
<td>CAP_SYS_TIME</td>
<td>Изменение системного времени</td>
</tr>
<tr>
<td>CAP_SYS_BOOT</td>
<td>Использование вызовов <span class="mono">reboot()</span> и <span class="mono">kexec_load()</span> - перезагрузка системы и "горячая" загрузка ядра, соответственно (<span class="mono">reboot(2)</span> <span class="mono">kexec_load(2)</span>)</td>
</tr>
<tr>
<td>CAP_SYS_ADMIN</td>
<td>Широкий спектр административных операций, как то: управление дисковыми квотами, активация и деактивация устройств своппинга и много чего ещё</td>
</tr>
<tr>
<td>CAP_SETGID<br />
CAP_SETUID</td>
<td>Манипуляции с идентификаторами пользователя/группы процесса - вызовы <span class="mono">setuid(2)</span>, <span class="mono">setreuid(2)</span>, <span class="mono">setresuid(2)</span>, <span class="mono">setfsuid(2</span>)</td>
</tr>
</table>
<p align="justify">Данный список далеко не полный, т.к. я не ставил целью переписывание соответствующей man-страницы, и лишь демонстрирует некоторые более или менее типичные привилегии. Все доступные для данного ядра привилегии можно посмотреть в заголовочном файле <span class="mono">include/uapi/linux/capability.h</span> или в man-странице <span class="mono">capabilities(2)</span>.</p>
<p align="justify">О флагах безопасности (securebits). Это флаги, управляющие наследованием и использованием привилегий. Этими флагами манипулируют сами потоки. Для этого, впрочем, им нужно иметь привилегию <span class="mono">CAP_SETPCAP</span>, как уже было упомянуто.</p>
<table width="70%" style="margin:0px auto;">
<col width="25%">
<col width="70%">
<tr>
<td>SECBIT_KEEP_CAPS</td>
<td>Поток, имеющий UID 0, сохраняет все разрешения при смене идентификатора пользователя (не root)<br /><br />По умолчанию этот флаг очищен и даже если он установлен потоком, он принудительно очищается при вызове exec*() с целью предотвращения утечки пивилегий</td>
</tr>
<tr>
<td>SECBIT_NO_SETUID_FIXUP</td>
<td>Множества разрешеий не будут корректироваться ядром при смене UID между 0 и любым другим значением</td>
</tr>
<tr>
<td>SECBIT_NOROOT</td>
<td>Программа с suid-битом не получает автоматически все привилегии, которые имеет root</td>
</tr>
</table>
<p align="justify">Управление флагами возможно с помощью <span class="mono">prctl(2)</span> и "операторов" <span class="mono">PR_SET_SECUREBITS</span> / <span class="mono">PR_GET_SECUREBITS</span>.</p>
<p align="justify">Так в общих чертах выглядит объект нашего интереса. Но встаёт вопрос, а кто и как назначает привилегии конкретным процессам? Когда поддержки файловых разрешений ещё не было, единственным способом использования привилегий был некий вспомогательный процесс, который мог бы раздавать их другим процессам. Этакий сервер безопасности. Либо сама программа, запущенная изначально с правами суперпользователя, могла добровольно отказаться от ненужных ей привилегий. Оба способа явно довольно вычурны. Вероятно это в немалой степени послужило фактором, сдерживающим внедрение использования привилегий.</p>
<h4>Файловые разрешения</h4>
<p align="justify">Вот мы и подошли, пожалуй, к самому интересному. Действительно, без файловых разрешений реализация привилегий в Linux так и осталась бы ущербной. Говоря выше о множествах привилегий, мы узнали, что множества доступных (permitted), наследуемых (inheritable) и эффективных (effective) привилегий свойственны как файлам, так и процессам. Что это значит? Ровно то, что значит. Привилегии могут быть ассоциированы не только с выполняющимся процессом, но и с файлом, в котором хранится исполняемый образ. Какой-то особой поддержки привилегий со стороны файловой системы не требуется. Всё делом в том, что разрешения хранятся в расширенных атрибутах файлов, а это значит, что любая файловая система, которая поддерживает расширенные атрибуты (а это, в общем-то, все современные файловые системы, которые широко используются в Linux - ext, reiserfs, xfs, например), автоматически поддерживает и файловые разрешения. Linux разделяет расширенные атрибуты файлов по пространствам имён. В частности, например, определены такие пространства, как "user" - для пользовательских атрибутов, "security" - для атрибутов безопасности. Нас интересует именно последнее. Разрешения хранятся в атрибуте capability. Имя атрибута определено в заголовке <span class="mono">include/uapi/linux/xattr.h</span> таким образом:</p><pre><span class="pre">#define XATTR_SECURITY_PREFIX "security."
</span><span class="operator">...</span><span class="pre">
#define XATTR_CAPS_SUFFIX "capability"
#define XATTR_NAME_CAPS XATTR_SECURITY_PREFIX XATTR_CAPS_SUFFIX</span></pre>
<p align="justify">Проведём небольшой эксперимент:</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ attr -l /usr/bin/dumpcap
Attribute "capability" has a 20 byte value for /usr/bin/dumpcap
# getcap /usr/bin/dumpcap
/usr/bin/dumpcap = cap_dac_read_search,cap_net_admin,cap_net_raw+ep</pre></span>
<p align="justify">Что и требовалось продемонстрировать. dumpcap является частью пакета wireshark. Именно этот исполняемый файл "хватает" пакеты с сетевого интерфейса. Для того, чтобы мы могли использовать wireshark из-под обычного пользовательского аккаунта, этому файлу присвоены соответствующие разрешения: операции сетевого администрирования, право на создание символьных ("сырых", raw) сокетов во множествах доступных и эффективных привилегий. Каким образом всё это облечено в код ядра? Вся чёрная магия скрыта в файле <span class="mono">security/commoncap.c</span></p>
<pre><span class="comment">/*
* Extract the on-exec-apply capability sets for an executable file.
*/</span><span class="type">
int</span> get_vfs_caps_from_disk<span class="operator">(</span><span class="keyword">const struct</span> dentry<span class="operator"> *</span>dentry<span class="operator">,</span><span class="keyword"> struct</span> cpu_vfs_cap_data<span class="operator"> *</span>cpu_caps<span class="operator">)
{</span><span class="keyword">
struct</span> inode<span class="operator"> *</span>inode<span class="operator"> =</span> dentry<span class="operator">-></span>d_inode<span class="operator">;</span>
__u32 magic_etc<span class="operator">;</span><span class="type">
unsigned</span> tocopy<span class="operator">,</span> i<span class="operator">;</span><span class="type">
int</span> size<span class="operator">;</span><span class="keyword">
struct</span> vfs_cap_data caps<span class="operator">;</span>
memset<span class="operator">(</span>cpu_caps<span class="operator">,</span><span class="int"> 0</span><span class="operator">,</span><span class="keyword"> sizeof</span><span class="operator">(</span><span class="keyword">struct</span> cpu_vfs_cap_data<span class="operator">));</span><span class="flow">
if</span><span class="operator"> (!</span>inode<span class="operator"> || !</span>inode<span class="operator">-></span>i_op<span class="operator">-></span>getxattr<span class="operator">)</span><span class="flow">
return</span><span class="operator"> -</span>ENODATA<span class="operator">;</span>
size<span class="operator"> =</span> inode<span class="operator">-></span>i_op<span class="operator">-></span>getxattr<span class="operator">((</span><span class="keyword">struct</span> dentry<span class="operator"> *)</span>dentry<span class="operator">,</span> XATTR_NAME_CAPS<span class="operator">, &</span>caps<span class="operator">,</span>
XATTR_CAPS_SZ<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>size<span class="operator"> == -</span>ENODATA<span class="operator"> ||</span> size<span class="operator"> == -</span>EOPNOTSUPP<span class="operator">)</span><span class="comment">
/* no data, that's ok */</span><span class="flow">
return</span><span class="operator"> -</span>ENODATA<span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>size<span class="operator"> <</span><span class="int"> 0</span><span class="operator">)</span><span class="flow">
return</span> size<span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>size<span class="operator"> <</span><span class="keyword"> sizeof</span><span class="operator">(</span>magic_etc<span class="operator">))</span><span class="flow">
return</span><span class="operator"> -</span>EINVAL<span class="operator">;</span>
cpu_caps<span class="operator">-></span>magic_etc<span class="operator"> =</span> magic_etc<span class="operator"> =</span> le32_to_cpu<span class="operator">(</span>caps<span class="operator">.</span>magic_etc<span class="operator">);</span><span class="flow">
switch</span><span class="operator"> (</span>magic_etc<span class="operator"> &</span> VFS_CAP_REVISION_MASK<span class="operator">) {</span><span class="flow">
case</span> VFS_CAP_REVISION_1<span class="operator">:</span><span class="flow">
if</span><span class="operator"> (</span>size<span class="operator"> !=</span> XATTR_CAPS_SZ_1<span class="operator">)</span><span class="flow">
return</span><span class="operator"> -</span>EINVAL<span class="operator">;</span>
tocopy<span class="operator"> =</span> VFS_CAP_U32_1<span class="operator">;</span><span class="flow">
break</span><span class="operator">;</span><span class="flow">
case</span> VFS_CAP_REVISION_2<span class="operator">:</span><span class="flow">
if</span><span class="operator"> (</span>size<span class="operator"> !=</span> XATTR_CAPS_SZ_2<span class="operator">)</span><span class="flow">
return</span><span class="operator"> -</span>EINVAL<span class="operator">;</span>
tocopy<span class="operator"> =</span> VFS_CAP_U32_2<span class="operator">;</span><span class="flow">
break</span><span class="operator">;</span><span class="flow">
default</span><span class="operator">:</span><span class="flow">
return</span><span class="operator"> -</span>EINVAL<span class="operator">;
}</span>
CAP_FOR_EACH_U32<span class="operator">(</span>i<span class="operator">) {</span><span class="flow">
if</span><span class="operator"> (</span>i<span class="operator"> >=</span> tocopy<span class="operator">)</span><span class="flow">
break</span><span class="operator">;</span>
cpu_caps<span class="operator">-></span>permitted<span class="operator">.</span>cap<span class="operator">[</span>i<span class="operator">] =</span> le32_to_cpu<span class="operator">(</span>caps<span class="operator">.</span>data<span class="operator">[</span>i<span class="operator">].</span>permitted<span class="operator">);</span>
cpu_caps<span class="operator">-></span>inheritable<span class="operator">.</span>cap<span class="operator">[</span>i<span class="operator">] =</span> le32_to_cpu<span class="operator">(</span>caps<span class="operator">.</span>data<span class="operator">[</span>i<span class="operator">].</span>inheritable<span class="operator">);
}</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;
}</span><span class="comment">
/*
* Attempt to get the on-exec apply capability sets for an executable file from
* its xattrs and, if present, apply them to the proposed credentials being
* constructed by execve().
*/</span><span class="keyword">
static</span><span class="type"> int</span> get_file_caps<span class="operator">(</span><span class="keyword">struct</span> linux_binprm<span class="operator"> *</span>bprm<span class="operator">,</span><span class="type"> bool</span><span class="operator"> *</span>effective<span class="operator">,</span><span class="type"> bool</span><span class="operator"> *</span>has_cap<span class="operator">)
{</span><span class="keyword">
struct</span> dentry<span class="operator"> *</span>dentry<span class="operator">;</span><span class="type">
int</span> rc<span class="operator"> =</span><span class="int"> 0</span><span class="operator">;</span><span class="keyword">
struct</span> cpu_vfs_cap_data vcaps<span class="operator">;</span>
bprm_clear_caps<span class="operator">(</span>bprm<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (!</span>file_caps_enabled<span class="operator">)</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>bprm<span class="operator">-></span>file<span class="operator">-></span>f_path<span class="operator">.</span>mnt<span class="operator">-></span>mnt_flags<span class="operator"> &</span> MNT_NOSUID<span class="operator">)</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;</span>
dentry<span class="operator"> =</span> dget<span class="operator">(</span>bprm<span class="operator">-></span>file<span class="operator">-></span>f_dentry<span class="operator">);</span>
rc<span class="operator"> =</span> get_vfs_caps_from_disk<span class="operator">(</span>dentry<span class="operator">, &</span>vcaps<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>rc<span class="operator"> <</span><span class="int"> 0</span><span class="operator">) {</span><span class="flow">
if</span><span class="operator"> (</span>rc<span class="operator"> == -</span>EINVAL<span class="operator">)</span>
printk<span class="operator">(</span>KERN_NOTICE<span class="string"> "%s: get_vfs_caps_from_disk returned %d for %s\n"</span><span class="operator">,</span>
__func__<span class="operator">,</span> rc<span class="operator">,</span> bprm<span class="operator">-></span>filename<span class="operator">);</span><span class="flow">
else if</span><span class="operator"> (</span>rc<span class="operator"> == -</span>ENODATA<span class="operator">)</span>
rc<span class="operator"> =</span><span class="int"> 0</span><span class="operator">;</span><span class="flow">
goto</span> out<span class="operator">;
}</span>
rc<span class="operator"> =</span> bprm_caps_from_vfs_caps<span class="operator">(&</span>vcaps<span class="operator">,</span> bprm<span class="operator">,</span> effective<span class="operator">,</span> has_cap<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>rc<span class="operator"> == -</span>EINVAL<span class="operator">)</span>
printk<span class="operator">(</span>KERN_NOTICE<span class="string"> "%s: cap_from_disk returned %d for %s\n"</span><span class="operator">,</span>
__func__<span class="operator">,</span> rc<span class="operator">,</span> bprm<span class="operator">-></span>filename<span class="operator">);</span>
out<span class="operator">:</span>
dput<span class="operator">(</span>dentry<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>rc<span class="operator">)</span>
bprm_clear_caps<span class="operator">(</span>bprm<span class="operator">);</span><span class="flow">
return</span> rc<span class="operator">;
}</span><span class="comment">
/**
* cap_bprm_set_creds - Set up the proposed credentials for execve().
* @bprm: The execution parameters, including the proposed creds
*
* Set up the proposed credentials for a new execution context being
* constructed by execve(). The proposed creds in @bprm->cred is altered,
* which won't take effect immediately. Returns 0 if successful, -ve on error.
*/</span><span class="type">
int</span> cap_bprm_set_creds<span class="operator">(</span><span class="keyword">struct</span> linux_binprm<span class="operator"> *</span>bprm<span class="operator">)
{</span><span class="keyword">
const struct</span> cred<span class="operator"> *</span>old<span class="operator"> =</span> current_cred<span class="operator">();</span><span class="keyword">
struct</span> cred<span class="operator"> *</span><span class="keyword">new</span><span class="operator"> =</span> bprm<span class="operator">-></span>cred<span class="operator">;</span><span class="type">
bool</span> effective<span class="operator">,</span> has_cap<span class="operator"> =</span><span class="bool"> false</span><span class="operator">;</span><span class="type">
int</span> ret<span class="operator">;</span>
kuid_t root_uid<span class="operator">;</span>
effective<span class="operator"> =</span><span class="bool"> false</span><span class="operator">;</span>
ret<span class="operator"> =</span> get_file_caps<span class="operator">(</span>bprm<span class="operator">, &</span>effective<span class="operator">, &</span>has_cap<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>ret<span class="operator"> <</span><span class="int"> 0</span><span class="operator">)</span><span class="flow">
return</span> ret<span class="operator">;</span>
root_uid<span class="operator"> =</span> make_kuid<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>user_ns<span class="operator">,</span><span class="int"> 0</span><span class="operator">);</span><span class="flow">
if</span><span class="operator"> (!</span>issecure<span class="operator">(</span>SECURE_NOROOT<span class="operator">)) {</span><span class="comment">
/*
* If the legacy file capability is set, then don't set privs
* for a setuid root binary run by a non-root user. Do set it
* for a root user just to cause least surprise to an admin.
*/</span><span class="flow">
if</span><span class="operator"> (</span>has_cap<span class="operator"> && !</span>uid_eq<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>uid<span class="operator">,</span> root_uid<span class="operator">) &&</span> uid_eq<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>euid<span class="operator">,</span> root_uid<span class="operator">)) {</span>
warn_setuid_and_fcaps_mixed<span class="operator">(</span>bprm<span class="operator">-></span>filename<span class="operator">);</span><span class="flow">
goto</span> skip<span class="operator">;
}</span><span class="comment">
/*
* To support inheritance of root-permissions and suid-root
* executables under compatibility mode, we override the
* capability sets for the file.
*
* If only the real uid is 0, we do not set the effective bit.
*/</span><span class="flow">
if</span><span class="operator"> (</span>uid_eq<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>euid<span class="operator">,</span> root_uid<span class="operator">) ||</span> uid_eq<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>uid<span class="operator">,</span> root_uid<span class="operator">)) {</span><span class="comment">
/* pP' = (cap_bset & ~0) | (pI & ~0) */</span><span class="keyword">
new</span><span class="operator">-></span>cap_permitted<span class="operator"> =</span> cap_combine<span class="operator">(</span>old<span class="operator">-></span>cap_bset<span class="operator">,</span>
old<span class="operator">-></span>cap_inheritable<span class="operator">);
}</span><span class="flow">
if</span><span class="operator"> (</span>uid_eq<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>euid<span class="operator">,</span> root_uid<span class="operator">))</span>
effective<span class="operator"> =</span><span class="bool"> true</span><span class="operator">;
}</span>
skip<span class="operator">:</span><span class="comment">
/* if we have fs caps, clear dangerous personality flags */</span><span class="flow">
if</span><span class="operator"> (!</span>cap_issubset<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>cap_permitted<span class="operator">,</span> old<span class="operator">-></span>cap_permitted<span class="operator">))</span>
bprm<span class="operator">-></span>per_clear<span class="operator"> |=</span> PER_CLEAR_ON_SETID<span class="operator">;</span><span class="comment">
/* Don't let someone trace a set[ug]id/setpcap binary with the revised
* credentials unless they have the appropriate permit.
*
* In addition, if NO_NEW_PRIVS, then ensure we get no new privs.
*/</span><span class="flow">
if</span><span class="operator"> ((!</span>uid_eq<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>euid<span class="operator">,</span> old<span class="operator">-></span>uid<span class="operator">) ||
!</span>gid_eq<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>egid<span class="operator">,</span> old<span class="operator">-></span>gid<span class="operator">) ||
!</span>cap_issubset<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>cap_permitted<span class="operator">,</span> old<span class="operator">-></span>cap_permitted<span class="operator">)) &&</span>
bprm<span class="operator">-></span>unsafe<span class="operator"> & ~</span>LSM_UNSAFE_PTRACE_CAP<span class="operator">) {</span><span class="comment">
/* downgrade; they get no more than they had, and maybe less */</span><span class="flow">
if</span><span class="operator"> (!</span>capable<span class="operator">(</span>CAP_SETUID<span class="operator">) ||
(</span>bprm<span class="operator">-></span>unsafe<span class="operator"> &</span> LSM_UNSAFE_NO_NEW_PRIVS<span class="operator">)) {</span><span class="keyword">
new</span><span class="operator">-></span>euid<span class="operator"> =</span><span class="keyword"> new</span><span class="operator">-></span>uid<span class="operator">;</span><span class="keyword">
new</span><span class="operator">-></span>egid<span class="operator"> =</span><span class="keyword"> new</span><span class="operator">-></span>gid<span class="operator">;
}</span><span class="keyword">
new</span><span class="operator">-></span>cap_permitted<span class="operator"> =</span> cap_intersect<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>cap_permitted<span class="operator">,</span>
old<span class="operator">-></span>cap_permitted<span class="operator">);
}</span><span class="keyword">
new</span><span class="operator">-></span>suid<span class="operator"> =</span><span class="keyword"> new</span><span class="operator">-></span>fsuid<span class="operator"> =</span><span class="keyword"> new</span><span class="operator">-></span>euid<span class="operator">;</span><span class="keyword">
new</span><span class="operator">-></span>sgid<span class="operator"> =</span><span class="keyword"> new</span><span class="operator">-></span>fsgid<span class="operator"> =</span><span class="keyword"> new</span><span class="operator">-></span>egid<span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>effective<span class="operator">)</span><span class="keyword">
new</span><span class="operator">-></span>cap_effective<span class="operator"> =</span><span class="keyword"> new</span><span class="operator">-></span>cap_permitted<span class="operator">;</span><span class="flow">
else</span>
cap_clear<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>cap_effective<span class="operator">);</span>
bprm<span class="operator">-></span>cap_effective<span class="operator"> =</span> effective<span class="operator">;</span><span class="comment">
/*
* Audit candidate if current->cap_effective is set
*
* We do not bother to audit if 3 things are true:
* 1) cap_effective has all caps
* 2) we are root
* 3) root is supposed to have all caps (SECURE_NOROOT)
* Since this is just a normal root execing a process.
*
* Number 1 above might fail if you don't have a full bset, but I think
* that is interesting information to audit.
*/</span><span class="flow">
if</span><span class="operator"> (!</span>cap_isclear<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>cap_effective<span class="operator">)) {</span><span class="flow">
if</span><span class="operator"> (!</span>cap_issubset<span class="operator">(</span>CAP_FULL_SET<span class="operator">,</span><span class="keyword"> new</span><span class="operator">-></span>cap_effective<span class="operator">) ||
!</span>uid_eq<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>euid<span class="operator">,</span> root_uid<span class="operator">) || !</span>uid_eq<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>uid<span class="operator">,</span> root_uid<span class="operator">) ||</span>
issecure<span class="operator">(</span>SECURE_NOROOT<span class="operator">)) {</span>
ret<span class="operator"> =</span> audit_log_bprm_fcaps<span class="operator">(</span>bprm<span class="operator">,</span><span class="keyword"> new</span><span class="operator">,</span> old<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>ret<span class="operator"> <</span><span class="int"> 0</span><span class="operator">)</span><span class="flow">
return</span> ret<span class="operator">;
}
}</span><span class="keyword">
new</span><span class="operator">-></span>securebits<span class="operator"> &= ~</span>issecure_mask<span class="operator">(</span>SECURE_KEEP_CAPS<span class="operator">);</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;
}</span></pre>
<p align="justify">Ключевой здесь является функция <span class="mono">get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps)</span>, принимающая в качестве аргументов указатель на элемент каталога, связанный с файлом исполняемого образа и указатель на структуру, которая принимает прочитанные из атрибута разрешения. <span class="mono">struct cpu_vfs_cap_data</span> - то же самое, что <span class="mono">struct vfs_cap_data</span> из <span class="mono">include/uapi/linux/capability.h</span>, но при этом числа представлены в специфичном для процессора порядке следования байтов (endianness), в то время, как для <span class="mono">struct vfs_cap_data</span> представление little endian. Атрибуты непосредственно считываются с диска при помощи метода <span class="mono">getxattr()</span> объекта, представляющего индексный узел:
<pre>size<span class="operator"> =</span> inode<span class="operator">-></span>i_op<span class="operator">-></span>getxattr<span class="operator">((</span><span class="keyword">struct</span> dentry<span class="operator"> *)</span>dentry<span class="operator">,</span> XATTR_NAME_CAPS<span class="operator">, &</span>caps<span class="operator">,</span> XATTR_CAPS_SZ<span class="operator">);</span></pre>
Если таковой метод реализован для индексного узла в данной файловой системе, конечно же. Эта функция непосредственно читает данные с диска и проверяет их валидность. Прежде всего, ревизия реализации разрешений должна соответствовать размеру дисковых данных для этой реализации. В свою очередь <span class="mono">get_vfs_caps_from_disk()</span> используется функцией <span class="mono">get_file_caps()</span>, которая вычисляет множество доступных процессу разрешений основываясь на данных, прочитанных с диска из расширенных атрибутов файла и том, что мы наследуем от родителя через <span class="mono">execve()</span>. Собственно последняя часть задачи ложится на функцию <span class="mono">bprm_caps_from_vfs_caps()</span>:</p>
<pre><span class="comment">/*
* Calculate the new process capability sets from the capability sets attached
* to a file.
*/</span><span class="keyword">
static inline</span><span class="type"> int</span> bprm_caps_from_vfs_caps<span class="operator">(</span><span class="keyword">struct</span> cpu_vfs_cap_data<span class="operator"> *</span>caps<span class="operator">,</span><span class="keyword">
struct</span> linux_binprm<span class="operator"> *</span>bprm<span class="operator">,</span><span class="type">
bool</span><span class="operator"> *</span>effective<span class="operator">,</span><span class="type">
bool</span><span class="operator"> *</span>has_cap<span class="operator">)
{</span><span class="keyword">
struct</span> cred<span class="operator"> *</span><span class="keyword">new</span><span class="operator"> =</span> bprm<span class="operator">-></span>cred<span class="operator">;</span><span class="type"> <span class="comment">/* Creds we inherit from the parent and simultaneously container for new creds */</span>
unsigned</span> i<span class="operator">;</span><span class="type">
int</span> ret<span class="operator"> =</span><span class="int"> 0</span><span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>caps<span class="operator">-></span>magic_etc<span class="operator"> &</span> VFS_CAP_FLAGS_EFFECTIVE<span class="operator">)
*</span>effective<span class="operator"> =</span><span class="bool"> true</span><span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>caps<span class="operator">-></span>magic_etc<span class="operator"> &</span> VFS_CAP_REVISION_MASK<span class="operator">)
*</span>has_cap<span class="operator"> =</span><span class="bool"> true</span><span class="operator">;</span>
CAP_FOR_EACH_U32<span class="operator">(</span>i<span class="operator">) {</span>
__u32 permitted<span class="operator"> =</span> caps<span class="operator">-></span>permitted<span class="operator">.</span>cap<span class="operator">[</span>i<span class="operator">];</span> <span class="comment">/* Those we have read from disk */</span>
__u32 inheritable<span class="operator"> =</span> caps<span class="operator">-></span>inheritable<span class="operator">.</span>cap<span class="operator">[</span>i<span class="operator">];</span><span class="comment">
/*
* pP' = (X & fP) | (pI & fI)
*/</span><span class="keyword">
new</span><span class="operator">-></span>cap_permitted<span class="operator">.</span>cap<span class="operator">[</span>i<span class="operator">] =
(</span><span class="keyword">new</span><span class="operator">-></span>cap_bset<span class="operator">.</span>cap<span class="operator">[</span>i<span class="operator">] &</span> permitted<span class="operator">) |
(</span><span class="keyword">new</span><span class="operator">-></span>cap_inheritable<span class="operator">.</span>cap<span class="operator">[</span>i<span class="operator">] &</span> inheritable<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>permitted<span class="operator"> & ~</span><span class="keyword">new</span><span class="operator">-></span>cap_permitted<span class="operator">.</span>cap<span class="operator">[</span>i<span class="operator">])</span><span class="comment">
/* insufficient to execute correctly */</span>
ret<span class="operator"> = -</span>EPERM<span class="operator">;
}</span><span class="comment">
/*
* For legacy apps, with no internal support for recognizing they
* do not have enough capabilities, we return an error if they are
* missing some "forced" (aka file-permitted) capabilities.
*/</span><span class="flow">
return</span><span class="operator"> *</span>effective<span class="operator"> ?</span> ret<span class="operator"> :</span><span class="int"> 0</span><span class="operator">;
}}</span></pre>
<p align="justify">В связи с упоминанием об этой функции, вспомним ещё раз о множествах. Исходя из файловых разрешений и привилегий процесса, вызывающего <span class="mono">exec*()</span>, результирующие разрешения для нового процесса вычисляются по следующим формулам:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset)
P'(effective) = F(effective) ? P'(permitted) : 0
P'(inheritable) = P(inheritable) [не изменяются]</pre></span>
Здесь F - файловые разрешения, P - привилегии родительского процесса, P' - результирующие привилегии. Ещё раз посмотрите на код функции <span class="mono">bprm_caps_from_vfs_caps()</span> и без труда поймёте, что к чему. Через один из своих аргументов, <span class="mono">effective</span>, <span class="mono">bprm_caps_from_vfs_caps()</span> также возвращает флаг, уведомляющий о наличии среди файловых разрешений таких, которые должны быть активированы - эффективных файловых разрешений. Вопреки ожиданию наличие этих разрешений определяется не их фактическим присутствием, а специальным флагом, <span class="mono">VFS_CAP_FLAGS_EFFECTIVE</span>. Старые разрешения передаются через блок параметров исполняемого файла - переменную типа <span class="mono">struct linux_binprm</span>. Поднимаясь снизу вверх, мы обнаружим, что <span class="mono">get_file_caps()</span> - это ещё отнюдь не верхушка айсберга. Эта функция в свою очередь вызывается из <span class="mono">cap_bprm_set_creds()</span>. Последняя не объявлена, как статичная, что указывает на простой факт - она используется где-то за пределами данной единицы компиляции. Проявив такие чудеса дедукции и порывшись вокруг, обнаруживаем, что на <span class="mono">cap_bprm_set_creds()</span> "ссылается" <span class="mono">static int selinux_bprm_set_creds(struct linux_binprm *bprm)</span> из <span class="mono">security/selinux/hooks.c</span>, напрямую последняя не вызывается. Что же касается <span class="mono">cap_bprm_set_creds()</span>, то именно она и является основным хабом, где происходит вся обработка, связанная с разрешениями. Говоря об этой функции, как о хабе, я имею в виду, что её вызывает execve() с проинициализированным блоком параметров исполняемого образа, отсюда же тракт управления идёт к считыванию данных с диска, и частичной обработке полученных данных и здесь же потом происходит окончательная обработка множеств разрешений. Результаты своей работы эта функция предоставляет в распоряжение execve() опять же через блок параметров файла. Переход к этому коду происходит через указатель на операции безопасности (security ops).</p>
<p align="justify">Чуть ранее, прежде чем управление будет передано <span class="mono">bprm_caps_from_vfs_caps()</span>, в локальной переменной <span class="mono">new</span>, куда <span class="mono">bprm_caps_from_vfs_caps()</span> запишет вычисленные разрешения, кто-то должен был <i>уже</i> что-то записать. Это понятно уже из самого кода <span class="mono">bprm_caps_from_vfs_caps()</span>, ибо в самом деле, довольно странный способ вычисления разрешений, применяя их к чему-то неопределённому. Разрешения передаются между разными участками кода через параметры исполняемого образа, <span class="mono">struct linux_binprm</span>. Эти параметры заполняются опять же, в <span class="mono">do_execve_common()</span>.</p>
<p align="justify">Так как в процесс загрузки и запуска образа на исполнение участвует очень много кода, лишь в самых общих чертах окинем взглядом всю цепь событий. На этот раз пойдём в обратном направлении - сверху вниз. Всё начинается с execve(): </p>
<pre><span class="comment">/*
* sys_execve() executes a new program.
*/</span><span class="keyword">
static</span><span class="type"> int</span> do_execve_common<span class="operator">(</span><span class="keyword">const</span><span class="type"> char</span><span class="operator"> *</span>filename<span class="operator">,</span><span class="keyword">
struct</span> user_arg_ptr argv<span class="operator">,</span><span class="keyword">
struct</span> user_arg_ptr envp<span class="operator">)
{
...</span>
retval<span class="operator"> =</span> prepare_bprm_creds<span class="operator">(</span>bprm<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>retval<span class="operator">)</span><span class="flow">
goto</span> out_free<span class="operator">;</span>
...
retval<span class="operator"> =</span> prepare_binprm<span class="operator">(</span>bprm<span class="operator">);
...</span></pre>
Инициализация множеств разрешений нового процесса:
<pre>
<span class="comment">/*
* Prepare credentials and lock ->cred_guard_mutex.
* install_exec_creds() commits the new creds and drops the lock.
* Or, if exec fails before, free_bprm() should release ->cred and
* and unlock.
*/</span>
<span class="type">int</span> prepare_bprm_creds<span class="operator">(</span><span class="keyword">struct</span> linux_binprm<span class="operator"> *</span>bprm<span class="operator">)
{</span><span class="flow">
if</span><span class="operator"> (</span>mutex_lock_interruptible<span class="operator">(&</span>current<span class="operator">-></span>signal<span class="operator">-></span>cred_guard_mutex<span class="operator">))</span><span class="flow">
return</span><span class="operator"> -</span>ERESTARTNOINTR<span class="operator">;</span>
bprm<span class="operator">-></span>cred<span class="operator"> =</span> prepare_exec_creds<span class="operator">();</span><span class="flow">
if</span><span class="operator"> (</span>likely<span class="operator">(</span>bprm<span class="operator">-></span>cred<span class="operator">))</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;</span>
mutex_unlock<span class="operator">(&</span>current<span class="operator">-></span>signal<span class="operator">-></span>cred_guard_mutex<span class="operator">);</span><span class="flow">
return</span><span class="operator"> -</span>ENOMEM<span class="operator">;
}</span></pre>
<pre><span class="comment">/*
* Prepare credentials for current to perform an execve()
* - The caller must hold ->cred_guard_mutex
*/</span><span class="keyword">
struct</span> cred<span class="operator"> *</span>prepare_exec_creds<span class="operator">(</span><span class="type">void</span><span class="operator">)
{</span><span class="keyword">
struct</span> cred<span class="operator"> *</span><span class="keyword">new</span><span class="operator">;</span><span class="keyword">
new</span><span class="operator"> =</span> prepare_creds<span class="operator">();</span><span class="flow">
if</span><span class="operator"> (!</span><span class="keyword">new</span><span class="operator">)</span><span class="flow">
return</span><span class="keyword"> new</span><span class="operator">;</span><span class="pre">
#ifdef CONFIG_KEYS
</span><span class="comment"> /* newly exec'd tasks don't get a thread keyring */</span>
key_put<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>thread_keyring<span class="operator">);</span><span class="keyword">
new</span><span class="operator">-></span>thread_keyring<span class="operator"> =</span> NULL<span class="operator">;</span><span class="comment">
/* inherit the session keyring; new process keyring */</span>
key_put<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>process_keyring<span class="operator">);</span><span class="keyword">
new</span><span class="operator">-></span>process_keyring<span class="operator"> =</span> NULL<span class="operator">;</span><span class="pre">
#endif
</span><span class="flow">
return</span><span class="keyword"> new</span><span class="operator">;
}</span></pre>
<pre><span class="comment">/**
* prepare_creds - Prepare a new set of credentials for modification
*
* Prepare a new set of task credentials for modification. A task's creds
* shouldn't generally be modified directly, therefore this function is used to
* prepare a new copy, which the caller then modifies and then commits by
* calling commit_creds().
*
* Preparation involves making a copy of the objective creds for modification.
*
* Returns a pointer to the new creds-to-be if successful, NULL otherwise.
*
* Call commit_creds() or abort_creds() to clean up.
*/</span><span class="keyword">
struct</span> cred<span class="operator"> *</span>prepare_creds<span class="operator">(</span><span class="type">void</span><span class="operator">)
{</span><span class="keyword">
struct</span> task_struct<span class="operator"> *</span>task<span class="operator"> =</span> current<span class="operator">;</span><span class="keyword">
const struct</span> cred<span class="operator"> *</span>old<span class="operator">;</span><span class="keyword">
struct</span> cred<span class="operator"> *</span><span class="keyword">new</span><span class="operator">;</span>
validate_process_creds<span class="operator">();</span><span class="keyword">
new</span><span class="operator"> =</span> kmem_cache_alloc<span class="operator">(</span>cred_jar<span class="operator">,</span> GFP_KERNEL<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (!</span><span class="keyword">new</span><span class="operator">)</span><span class="flow">
return</span> NULL<span class="operator">;</span>
kdebug<span class="operator">(</span><span class="string">"prepare_creds() alloc %p"</span><span class="operator">,</span><span class="keyword"> new</span><span class="operator">);</span>
old<span class="operator"> =</span> task<span class="operator">-></span>cred<span class="operator">;</span>
memcpy<span class="operator">(</span><span class="keyword">new</span><span class="operator">,</span> old<span class="operator">,</span><span class="keyword"> sizeof</span><span class="operator">(</span><span class="keyword">struct</span> cred<span class="operator">));</span>
atomic_set<span class="operator">(&</span><span class="keyword">new</span><span class="operator">-></span>usage<span class="operator">,</span><span class="int"> 1</span><span class="operator">);</span>
set_cred_subscribers<span class="operator">(</span><span class="keyword">new</span><span class="operator">,</span><span class="int"> 0</span><span class="operator">);</span>
get_group_info<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>group_info<span class="operator">);</span>
get_uid<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>user<span class="operator">);</span>
get_user_ns<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>user_ns<span class="operator">);</span><span class="pre">
#ifdef CONFIG_KEYS
</span> key_get<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>session_keyring<span class="operator">);</span>
key_get<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>process_keyring<span class="operator">);</span>
key_get<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>thread_keyring<span class="operator">);</span>
key_get<span class="operator">(</span><span class="keyword">new</span><span class="operator">-></span>request_key_auth<span class="operator">);</span><span class="pre">
#endif
#ifdef CONFIG_SECURITY
</span><span class="keyword"> new</span><span class="operator">-></span>security<span class="operator"> =</span> NULL<span class="operator">;</span><span class="pre">
#endif
</span><span class="flow">
if</span><span class="operator"> (</span>security_prepare_creds<span class="operator">(</span><span class="keyword">new</span><span class="operator">,</span> old<span class="operator">,</span> GFP_KERNEL<span class="operator">) <</span><span class="int"> 0</span><span class="operator">)</span><span class="flow">
goto</span> error<span class="operator">;</span>
validate_creds<span class="operator">(</span><span class="keyword">new</span><span class="operator">);</span><span class="flow">
return</span><span class="keyword"> new</span><span class="operator">;</span>
error<span class="operator">:</span>
abort_creds<span class="operator">(</span><span class="keyword">new</span><span class="operator">);</span><span class="flow">
return</span> NULL<span class="operator">;
}</span>
EXPORT_SYMBOL<span class="operator">(</span>prepare_creds<span class="operator">);</span></pre>
Данные просто копируются из дескриптора текущей задачи, т.е. вызвавшей exec(), в блок параметров образа, который будет запущен. Теперь весь остальной код может сделать свою работу.</p>
<p align="justify">Далее, считывание разрешений с диска и вычисление новых разрешений, там же в <span class="mono">fs/exec.c</span>
<pre><span class="comment">/*
* Fill the binprm structure from the inode.
* Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes
*
* This may be called multiple times for binary chains (scripts for example).
*/</span><span class="type">
int</span> prepare_binprm<span class="operator">(</span><span class="keyword">struct</span> linux_binprm<span class="operator"> *</span>bprm<span class="operator">)
{</span>
umode_t mode<span class="operator">;</span><span class="keyword">
struct</span> inode<span class="operator"> *</span> inode<span class="operator"> =</span> file_inode<span class="operator">(</span>bprm<span class="operator">-></span>file<span class="operator">);</span><span class="type">
int</span> retval<span class="operator">;</span>
mode<span class="operator"> =</span> inode<span class="operator">-></span>i_mode<span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>bprm<span class="operator">-></span>file<span class="operator">-></span>f_op<span class="operator"> ==</span> NULL<span class="operator">)</span><span class="flow">
return</span><span class="operator"> -</span>EACCES<span class="operator">;</span><span class="comment">
/* clear any previous set[ug]id data from a previous binary */</span>
bprm<span class="operator">-></span>cred<span class="operator">-></span>euid<span class="operator"> =</span> current_euid<span class="operator">();</span>
bprm<span class="operator">-></span>cred<span class="operator">-></span>egid<span class="operator"> =</span> current_egid<span class="operator">();</span><span class="flow">
if</span><span class="operator"> (!(</span>bprm<span class="operator">-></span>file<span class="operator">-></span>f_path<span class="operator">.</span>mnt<span class="operator">-></span>mnt_flags<span class="operator"> &</span> MNT_NOSUID<span class="operator">) &&
!</span>current<span class="operator">-></span>no_new_privs<span class="operator"> &&</span>
kuid_has_mapping<span class="operator">(</span>bprm<span class="operator">-></span>cred<span class="operator">-></span>user_ns<span class="operator">,</span> inode<span class="operator">-></span>i_uid<span class="operator">) &&</span>
kgid_has_mapping<span class="operator">(</span>bprm<span class="operator">-></span>cred<span class="operator">-></span>user_ns<span class="operator">,</span> inode<span class="operator">-></span>i_gid<span class="operator">)) {</span><span class="comment">
/* Set-uid? */</span><span class="flow">
if</span><span class="operator"> (</span>mode<span class="operator"> &</span> S_ISUID<span class="operator">) {</span>
bprm<span class="operator">-></span>per_clear<span class="operator"> |=</span> PER_CLEAR_ON_SETID<span class="operator">;</span>
bprm<span class="operator">-></span>cred<span class="operator">-></span>euid<span class="operator"> =</span> inode<span class="operator">-></span>i_uid<span class="operator">;
}</span><span class="comment">
/* Set-gid? */
/*
* If setgid is set but no group execute bit then this
* is a candidate for mandatory locking, not a setgid
* executable.
*/</span><span class="flow">
if</span><span class="operator"> ((</span>mode<span class="operator"> & (</span>S_ISGID<span class="operator"> |</span> S_IXGRP<span class="operator">)) == (</span>S_ISGID<span class="operator"> |</span> S_IXGRP<span class="operator">)) {</span>
bprm<span class="operator">-></span>per_clear<span class="operator"> |=</span> PER_CLEAR_ON_SETID<span class="operator">;</span>
bprm<span class="operator">-></span>cred<span class="operator">-></span>egid<span class="operator"> =</span> inode<span class="operator">-></span>i_gid<span class="operator">;
}
}</span><span class="comment">
/* fill in binprm security blob */</span>
retval<span class="operator"> =</span> security_bprm_set_creds<span class="operator">(</span>bprm<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>retval<span class="operator">)</span><span class="flow">
return</span> retval<span class="operator">;</span>
bprm<span class="operator">-></span>cred_prepared<span class="operator"> =</span><span class="int"> 1</span><span class="operator">;</span>
memset<span class="operator">(</span>bprm<span class="operator">-></span>buf<span class="operator">,</span><span class="int"> 0</span><span class="operator">,</span> BINPRM_BUF_SIZE<span class="operator">);</span><span class="flow">
return</span> kernel_read<span class="operator">(</span>bprm<span class="operator">-></span>file<span class="operator">,</span><span class="int"> 0</span><span class="operator">,</span> bprm<span class="operator">-></span>buf<span class="operator">,</span> BINPRM_BUF_SIZE<span class="operator">);
}</span></pre>
Далее, в <span class="mono">security/security.c</span>
<pre><span class="type">int</span> security_bprm_set_creds<span class="operator">(</span><span class="keyword">struct</span> linux_binprm<span class="operator"> *</span>bprm<span class="operator">)
{</span><span class="flow">
return</span> security_ops<span class="operator">-></span>bprm_set_creds<span class="operator">(</span>bprm<span class="operator">);
}</span></pre>
Структура, содержащая указатели на операции безопасности, инициализируется в security/selinux/hooks.c. Здесь без труда находим наш selinux_bprm_set_creds()
<pre><span class="keyword">static struct</span> security_operations selinux_ops<span class="operator"> = {
.</span>name<span class="operator"> =</span><span class="string"> "selinux"</span><span class="operator">,
...
.</span>bprm_set_creds<span class="operator"> =</span> selinux_bprm_set_creds<span class="operator">,
...
};</span></pre>
Резюмируя, получим такой вот флоучарт:
<div align="center">
<img alt="" src="data:image/gif;base64,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" /></div>
<p align="justify">Надеюсь, это головокружительное путешествие помогло понять, каким образом реализованы привилегии Linux и что делает их привлекательными, надеюсь, вскользь коснувшись кода, вы поймёте, какую роль в реализации поддержки привилегий играет VFS. К сожалению, написано уже и так больше, чем я предполагал изначально, а во все детали заглянуть всё равно не представляется возможным в заметке сколь-нибудь вменяемого объёма. Поэтому напоследок только взглянем краем глаза, как на практике реализуется контроль привилегий и на этом остановимся. Рассмотрим самый простой пример, с которого мы и начали, с настройкой системных часов. Наша программа вызывает stime(). Так как представить себе использование этого системного вызова совсем не сложно, текст самой программы не приведён:</p>
<span class="mono">kernel/time.c</span>
<pre><span class="comment"> /*
* sys_stime() can be implemented in user-level using
* sys_settimeofday(). Is this for backwards compatibility? If so,
* why not move it into the appropriate arch directory (for those
* architectures that need it).
*/</span>
SYSCALL_DEFINE1<span class="operator">(</span>stime<span class="operator">,</span> time_t __user<span class="operator"> *,</span> tptr<span class="operator">)
{</span><span class="keyword">
struct</span> timespec tv<span class="operator">;</span><span class="type">
int</span> err<span class="operator">;</span><span class="flow">
if</span><span class="operator"> (</span>get_user<span class="operator">(</span>tv<span class="operator">.</span>tv_sec<span class="operator">,</span> tptr<span class="operator">))</span><span class="flow">
return</span><span class="operator"> -</span>EFAULT<span class="operator">;</span>
tv<span class="operator">.</span>tv_nsec<span class="operator"> =</span><span class="int"> 0</span><span class="operator">;</span>
err<span class="operator"> =</span> security_settime<span class="operator">(&</span>tv<span class="operator">,</span> NULL<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>err<span class="operator">)</span><span class="flow">
return</span> err<span class="operator">;</span>
do_settimeofday<span class="operator">(&</span>tv<span class="operator">);</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;
}</span></pre>
<span class="mono">include/linux/security.h</span>
<pre><span class="keyword">static inline</span><span class="type"> int</span> security_settime<span class="operator">(</span><span class="keyword">const struct</span> timespec<span class="operator"> *</span>ts<span class="operator">,</span><span class="keyword">
const struct</span> timezone<span class="operator"> *</span>tz<span class="operator">)
{</span><span class="flow">
return</span> cap_settime<span class="operator">(</span>ts<span class="operator">,</span> tz<span class="operator">);
}</span></pre>
<span class="mono">security/commoncap.c</span>
<pre><span class="type">int</span> cap_settime<span class="operator">(</span><span class="keyword">const struct</span> timespec<span class="operator"> *</span>ts<span class="operator">,</span><span class="keyword"> const struct</span> timezone<span class="operator"> *</span>tz<span class="operator">)
{</span><span class="flow">
if</span><span class="operator"> (!</span>capable<span class="operator">(</span>CAP_SYS_TIME<span class="operator">))</span><span class="flow">
return</span><span class="operator"> -</span>EPERM<span class="operator">;</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;
}</span></pre>
<span class="mono">kernel/capability.c</span>
<pre><span class="comment">/**
* capable - Determine if the current task has a superior capability in effect
* @cap: The capability to be tested for
*
* Return true if the current task has the given superior capability currently
* available for use, false if not.
*
* This sets PF_SUPERPRIV on the task if the capability is available on the
* assumption that it's about to be used.
*/</span><span class="type">
bool</span> capable<span class="operator">(</span><span class="type">int</span> cap<span class="operator">)
{</span><span class="flow">
return</span> ns_capable<span class="operator">(&</span>init_user_ns<span class="operator">,</span> cap<span class="operator">);
}</span></pre>
<pre><span class="comment">/**
* ns_capable - Determine if the current task has a superior capability in effect
* @ns: The usernamespace we want the capability in
* @cap: The capability to be tested for
*
* Return true if the current task has the given superior capability currently
* available for use, false if not.
*
* This sets PF_SUPERPRIV on the task if the capability is available on the
* assumption that it's about to be used.
*/</span><span class="type">
bool</span> ns_capable<span class="operator">(</span><span class="keyword">struct</span> user_namespace<span class="operator"> *</span>ns<span class="operator">,</span><span class="type"> int</span> cap<span class="operator">)
{</span><span class="flow">
if</span><span class="operator"> (</span>unlikely<span class="operator">(!</span>cap_valid<span class="operator">(</span>cap<span class="operator">))) {</span>
printk<span class="operator">(</span>KERN_CRIT<span class="string"> "capable() called with invalid cap=%u\n"</span><span class="operator">,</span> cap<span class="operator">);</span>
BUG<span class="operator">();
}</span><span class="flow">
if</span><span class="operator"> (</span>security_capable<span class="operator">(</span>current_cred<span class="operator">(),</span> ns<span class="operator">,</span> cap<span class="operator">) ==</span><span class="int"> 0</span><span class="operator">) {</span>
current<span class="operator">-></span>flags<span class="operator"> |=</span> PF_SUPERPRIV<span class="operator">;</span><span class="flow">
return</span><span class="bool"> true</span><span class="operator">;
}</span><span class="flow">
return</span><span class="bool"> false</span><span class="operator">;
}</span>
EXPORT_SYMBOL<span class="operator">(</span>ns_capable<span class="operator">);</span></pre>
<span class="mono">security/selinux/hooks.c</span>
<pre><span class="comment">/*
* (This comment used to live with the selinux_task_setuid hook,
* which was removed).
*
* Since setuid only affects the current process, and since the SELinux
* controls are not based on the Linux identity attributes, SELinux does not
* need to control this operation. However, SELinux does control the use of
* the CAP_SETUID and CAP_SETGID capabilities using the capable hook.
*/</span><span class="keyword">
static</span><span class="type"> int</span> selinux_capable<span class="operator">(</span><span class="keyword">const struct</span> cred<span class="operator"> *</span>cred<span class="operator">,</span><span class="keyword"> struct</span> user_namespace<span class="operator"> *</span>ns<span class="operator">,</span><span class="type">
int</span> cap<span class="operator">,</span><span class="type"> int</span> audit<span class="operator">)
{</span><span class="type">
int</span> rc<span class="operator">;</span>
rc<span class="operator"> =</span> cap_capable<span class="operator">(</span>cred<span class="operator">,</span> ns<span class="operator">,</span> cap<span class="operator">,</span> audit<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>rc<span class="operator">)</span><span class="flow">
return</span> rc<span class="operator">;</span><span class="flow">
return</span> cred_has_capability<span class="operator">(</span>cred<span class="operator">,</span> cap<span class="operator">,</span> audit<span class="operator">);
}</span></pre>
<span class="mono">security/commoncap.c</span>
<pre><span class="comment"> /**
* cap_capable - Determine whether a task has a particular effective capability
* @cred: The credentials to use
* @ns: The user namespace in which we need the capability
* @cap: The capability to check for
* @audit: Whether to write an audit message or not
*
* Determine whether the nominated task has the specified capability amongst
* its effective set, returning 0 if it does, -ve if it does not.
*
* NOTE WELL: cap_has_capability() cannot be used like the kernel's capable()
* and has_capability() functions. That is, it has the reverse semantics:
* cap_has_capability() returns 0 when a task has a capability, but the
* kernel's capable() and has_capability() returns 1 for this case.
*/</span><span class="type">
int</span> cap_capable<span class="operator">(</span><span class="keyword">const struct</span> cred<span class="operator"> *</span>cred<span class="operator">,</span><span class="keyword"> struct</span> user_namespace<span class="operator"> *</span>targ_ns<span class="operator">,</span><span class="type">
int</span> cap<span class="operator">,</span><span class="type"> int</span> audit<span class="operator">)
{</span><span class="keyword">
struct</span> user_namespace<span class="operator"> *</span>ns<span class="operator"> =</span> targ_ns<span class="operator">;</span><span class="comment">
/* See if cred has the capability in the target user namespace
* by examining the target user namespace and all of the target
* user namespace's parents.
*/</span><span class="flow">
for</span><span class="operator"> (;;) {</span><span class="comment">
/* Do we have the necessary capabilities? */</span><span class="flow">
if</span><span class="operator"> (</span>ns<span class="operator"> ==</span> cred<span class="operator">-></span>user_ns<span class="operator">)</span><span class="flow">
return</span> cap_raised<span class="operator">(</span>cred<span class="operator">-></span>cap_effective<span class="operator">,</span> cap<span class="operator">) ?</span><span class="int"> 0</span><span class="operator"> : -</span>EPERM<span class="operator">;</span><span class="comment">
/* Have we tried all of the parent namespaces? */</span><span class="flow">
if</span><span class="operator"> (</span>ns<span class="operator"> == &</span>init_user_ns<span class="operator">)</span><span class="flow">
return</span><span class="operator"> -</span>EPERM<span class="operator">;</span><span class="comment">
/*
* The owner of the user namespace in the parent of the
* user namespace has all caps.
*/</span><span class="flow">
if</span><span class="operator"> ((</span>ns<span class="operator">-></span>parent<span class="operator"> ==</span> cred<span class="operator">-></span>user_ns<span class="operator">) &&</span> uid_eq<span class="operator">(</span>ns<span class="operator">-></span>owner<span class="operator">,</span> cred<span class="operator">-></span>euid<span class="operator">))</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;</span><span class="comment">
/*
* If you have a capability in a parent user ns, then you have
* it over all children user namespaces as well.
*/</span>
ns<span class="operator"> =</span> ns<span class="operator">-></span>parent<span class="operator">;
}</span><span class="comment">
/* We never get here */</span><span class="operator">
}</span></pre>
<p align="justify">В общем-то, не то, чтобы всё это было очень сложно. Идея довольно проста. Запутанность привносит модульная модель безопасности. Но за стенами кода скрыт достаточно простой механизм. В нашем примере <span class="mono">security_settime()</span> не имеет никакого отношения к реальной настройке часов. Всё, что делает эта функция, проверяет наличие соответствующей привилегии. Через хуки SELinux управляющий тракт в конечном итоге приводит нас в код, находящийся в файле security/commoncap.c, где макрос <span class="mono">cap_raised()</span> производит тривиальную проверку (логическое И между имеющимся у процесса разрешением и требуемой привелегией - соответствующая привилегия из эффективного множества должна быть установлена в единицу). В качестве упражнения оставляю вам удовольствие найти, что за <span class="mono">security_settime()</span> и как мы выходим на описанный выше тракт :)</p>
<h4>Почти живой пример</h4>
<p align="justify">И в завершение совсем немного практики. Признаюсь честно, мне было уже лень писать код, поэтому я стащил небольшой кусочек <a href="http://blog.siphos.be/2013/05/overview-of-linux-capabilities-part-2/">отсюда</a>.
<pre><span class="pre">#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <unistd.h>
</span><span class="type">
int</span><span class="keyword"> main</span><span class="operator">(</span><span class="type">int</span> argc<span class="operator">,</span><span class="type"> char</span><span class="operator"> **</span> argv<span class="operator">)
{</span>
printf<span class="operator"> (</span><span class="string">"cap_setuid and cap_setgid: %d\n"</span><span class="operator">,</span> prctl<span class="operator"> (</span>PR_CAPBSET_READ<span class="operator">,</span> CAP_SETUID<span class="operator"> |</span> CAP_SETGID<span class="operator">,</span><span class="int"> 0</span><span class="operator">,</span><span class="int"> 0</span><span class="operator">,</span><span class="int"> 0</span><span class="operator">));</span>
printf<span class="operator"> (</span><span class="string">" %s\n"</span><span class="operator">,</span> cap_to_text<span class="operator"> (</span>cap_get_file<span class="operator"> (</span>argv<span class="operator"> [</span><span class="int">0</span><span class="operator">]),</span> NULL<span class="operator">));</span>
printf<span class="operator"> (</span><span class="string">" %s\n"</span><span class="operator">,</span> cap_to_text<span class="operator"> (</span>cap_get_proc<span class="operator"> (),</span> NULL<span class="operator">));</span><span class="flow">
if</span><span class="operator"> (</span>setresuid<span class="operator"> (</span><span class="int">0</span><span class="operator">,</span><span class="int"> 0</span><span class="operator">,</span><span class="int"> 0</span><span class="operator">));</span>
printf<span class="operator"> (</span><span class="string">"setresuid(): %s\n"</span><span class="operator">,</span> strerror<span class="operator"> (</span>errno<span class="operator">));</span>
execve<span class="operator"> (</span><span class="string">"/bin/sh"</span><span class="operator">,</span> NULL<span class="operator">,</span> NULL<span class="operator">);
}</span>
</pre>
Эта программка должна изменить свой UID и дать нам шелл суперпользователя. Что ж, попытаем счастья:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ gcc -o cap_test cap_test.c -lcap
$ ./cap_test
cap_setuid and cap_setgid: 1
=
=
setresuid(): Operation not permitted</pre></span>
Не повезло :( Но мы-то уже знаем, дело вовсе не в везении, а в том, что наш файл не имеет соответствующих разрешений. Попробуем теперь вот так:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># setcap cap_setuid,cap_setgid+ep ./cap_test
$ ./cap_test
cap_setuid and cap_setgid: 1
= cap_setgid,cap_setuid+ep
= cap_setgid,cap_setuid+ep
setresuid(): Success
# exit
exit</pre></span>
Успех! Да, кстати, на всякий случай, не забывайте выходить из шеллов, т.к. даже при безуспешном вызове setresuid() эта программа очевидным образом спавнит новый шелл каждый раз, только без привилегий :) В первой строке она показывает файловые разрешения, во второй - разрешения выполняющегося процесса. Так что при желании можете поэкспериментировать с этим примером больше. Информацию о разрешениях выполняющегося процесса можно посмотреть через /proc:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ grep ^Cap /proc/$$/status
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000001fffffffff</pre></span>
Причём, не имея прав суперпользователя, вы ничего интересного не увидите, кроме маски. Вот так - уже интереснее:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># grep ^Cap /proc/$$/status
CapInh: 0000000000000000
CapPrm: 0000001fffffffff
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff</pre></span>
Значения можно декодировать с помощью capsh:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ capsh --decode=0000001fffffffff</pre></span>
Также обратите внимание на утиль pscap, который показывает разрешения выполняющихся процессов. Всё сказанное наталкивает на мысль, что теоретически root, как таковой, уже не сильно нужен и вместо него можно ввести пользователя с полным набором привилегий. Кроме того, такая модель позволяет создавать промежуточных, недо-root'ов, с усечёнными полномочиями. Правда это требует обязательного включения поддержки некоторых параметров ядра при сборке, которые могут быть и отключены в пользовательских ядрах, так что модель root'а всё же пока актуальна, так как остаётся универсальным запасным вариантом для всех вариантов сборки ядра Linux.</p>
<p align="justify">PS: в заметке довольно много кода. Изначально я хотел спрятать всё это безобразие под <s>коврик</s>кат, но он работал не совсем так, как мне бы хотелось и я решил, что пусть уж лучше будет так, чем сломанный функционал. Надеюсь, к следующему разу я найду какой-нибудь рабочий вариант.</p>
<h4>Источники</h4><p>
[1] <a href="http://www.cis.syr.edu/~wedu/seed/Documentation/Linux/How_Linux_Capability_Works.pdf">How Linux Capability Works in 2.6.25</a><br />
[2] <a href="http://ols.fedoraproject.org/OLS/Reprints-2008/hallyn-reprint.pdf">Linux Capabilities: making them work</a><br />
</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-32032249942666794482013-11-29T05:04:00.000-08:002013-12-01T02:41:22.172-08:00Исключения и... OS X, XNU, Mach<style type="text/css">
.source { display: inline-block; width: 8; }
.comment { color: #999999; font-style: italic; }
.pre { color: #000099; }
.string { color: #009900; }
.char { color: #009900; }
.float { color: #996600; }
.int { color: #999900; }
.bool { color: #000000; font-weight: bold; }
.type { color: #FF6633; }
.flow { color: #FF0000; }
.keyword { color: #990000; }
.operator { color: #663300; font-weight: bold; }
.operator { color: #663300; font-weight: bold; }
.mono { font-family: "Courier New", Courier, monospace; }
td { border-top: 1px solid black; border-collapse: collapse; }
</style>
<p align="justify">Вот так вот, совершенно внезапно. Ну что ж, раз по работе мне приходится иметь дело не только (и даже далеко не столько) с Linux, то... Почему бы, собственно, и нет?</p>
<p align="justify">Вообще, исключения - они и в Африке исключения. Они бывают и в Linux, и в Windows. Что же такого необычного в исключениях OS X? На самом деле, много чего. И дело именно в том, как всё это реализовано. Хотелось бы (и придётся) о многом в этой связи поговорить и написать, но начнём с самого начала, как говорится.</p>
<p align="justify">Обсуждаемая тема не является каким-то нововведением OS X и не является её эксклюзивным атрибутом, как формат исполняемых файлов Mach-O (напомню, что на данный момент в мире Unix везде воцарился ELF, Windows использует родственный формат - PE). Модель обработки исключений в OS X не является чем-то уникальным. Это прямое следствие того факта, что ядро OS X, XNU, построено на базе микроядра CMU Mach. Причём, XNU само по себе микроядром не является. Интересна в целом модель обработки исключений в Mach [3]. К слову, ядро XNU является открытым и его исходные коды даже доступны для скачивания вот <a href="http://opensource.apple.com">здесь</a>. Речь именно о ядре. Многие его расширения (kernel extensions, kext - драйверы или модули в терминологии Linux) являются проприетарными. Исходным кодом некоторых Apple всё же делится, но большинство закрыты. Не ищите там соусы iOS - она почти целиком и полностью закрыта, включая ядро, которым является всё тот же XNU, как ни странно. Обычно версия ядра настольной OS X отстаёт от версии оного для iOS. XNU iOS содержит специфичный для ARM код плюс новые чудесные плюшки. Код ARM в ядре настольной OS X почти не фигурирует, как и некоторые специфичные для iOS сервисы. Однако, об iOS я ничего писать не могу и не буду, ибо у меня нет ни одного устройства, которым она могла бы управлять и сама по себе она мне в меньшей степени интересна. Самым интересным, что может дать iOS - джейлбрейкингом, я также не занимаюсь по упомянутой причине.</p>
<p align="justify">Так вот, XNU в свою очередь является не полностью оригинальной разработкой Apple, а построен на микроядре Mach, хотя отказать в оригинальности разработчикам XNU значило бы сильно покривить душой против истины. Не смотря на родство с Mach, ядро OS X, XNU, микроядром не является. И более того, OS X никогда микроядра не имела, а большой и медленной была совсем по другой причине. XNU - это довольно интересный сплав Mach и BSD, работающих в одном адресном пространстве. Т.е., именно поэтому XNU не является чистым дериватом Mach и именно поэтому рассматриваемая тема не является исключительно OS X-центричной. Она могла бы в большой степени быть применимой и к GNU/Hurd по той простой причине, что GNU/Hurd работает на ядре GNU Mach. В некотором смысле это более каноничный вариант Mach. В отличие от XNU, в котором не стали пытаться сделать ОС на микроядре, в GNU/Hurd решили ничего особо радикально не менять в модели системы, для которой предусмотрено ядро такого типа.</p>
<p align="justify">Что же такого необычного и интересного с исключениями в частности и вообще всеми этими разновидностями Mach? Необычна идея Mach сама по себе. Она была необычной в 80-х, когда начался проект CMU Mach, и, я мог бы даже смело утверждать, остаётся таковой по сей день из-за малой распространённости ОС на микроядре. Микро - ключевой компонент. На данный момент автору вообще не приходит на ум ни одной коммерчески успешной или просто популярной реализации ОС на микроядре (Minix разве что? QNX весьма специфичен и даже на микроядро не тянет - скорее это уже наноядро и хоть проект действительно продемонстрировал, что уделом альтернативных архитектур не обязательно является безвестная смерть в академиях. В целом, ситуация всё же именно такова - микроядро не смогло найти или пробить себе дорогу на "компьютер в каждый дом"). Микроядерная архитектура per se даже спустя столько лет больше является экзотикой и просто интересностью. И тем не менее, хоть все популярные/успешные проекты используют либо классическую монолитную архитектуру (с поддержкой модульности - сейчас без неё уже никуда), либо гибридную, идеи и заветы микроядра не остались преданы полному забвению. Яркий пример - всё та же OS X.</p>
<p align="justify">Основа микроядерной архитектуры - обмен сообщениями, разновидность межпроцессного взаимодействия (IPC). Интересно отметить, что обмен сообщениями, как разновидность RPC в пределах хоста, есть и в Windows. В NT 3.5 был LPC - local procedure call, а ныне есть ALPC - advanced local procedure call, играющий если не ключевую, то значительную роль в работе некоторых критичных системных служб. В Mach сообщения - универсальный клей системы, который не только позволяет общаться процессам друг с другом (даже если процессы и вовсе работают на разных системах - таков был первоначальный дизайн Mach, но этот потенциал остался невостребованным в OS X), но и являются тем способом, которым приложение может запросить некий сервис системы. Если в ОС с монолитным/гибридным ядром сервисы ядра доступны через ловушки (трапы) и разнообразие этих сервисов весьма велико, как того требует время и технический прогресс в данной области, то в ОС на основе микроядра само ядро предоставляет лишь небольшое число таких сервисов, которые ориентированы именно на передачу сообщений. Так, в микроядерной архитектуре реализация файловой системы является не частью ядра. Довольно грубо говоря, в микроядерной ОС есть процесс, который умеет работать с данной файловой системой (сервер файловой системы) и есть процесс, заинтересованный в доступе к тому, что хранится на файловой системе. Второй процесс в таком случае тыкает в бок первый процесс и вежливо просит его прочитать вооон тот файлик с диска. Само ядро слыхом не слыхивало ни о каких таких файловых системах, да и о файлах оно, в общем-то, понятия не имеет. Для того, чтобы в нашем примере два процесса смогли между собой пообщаться и вообще, получить какую-то пользу друг от друга (а пользователь, таким образом, получит пользу от компьютера) и нужны сообщения в микроядерной архитектуре. Ядро является маршрутизатором этих сообщений и оно должно обеспечивать всю необходимую поддержку системы обмена сообщений, что включает управление памятью (копирование сообщений из адресного пространства клиента в адресное пространство сервера, а также обратно, или отображение памяти в случае больших объёмов передаваемых данных), планирование потоков и, наконец, саму подсистему IPC. Больше микроядро никому ничего не должно.</p>
<p align="justify">В Mach обмен сообщениями осуществляется через порты (Mach ports, не спутайте с MacPorts). Порт - это конечная точка коммуникационного канала. С точки зрения пользователя порт - это просто целое число, как дескриптор файла в Un*x, т.е., это совершенно непрозрачный объект, над которым можно производить чётко определённые операции. Порт локален по отношению к процессу (task, задаче, в терминологии Mach), опять же и так же, как файловые дескрипторы Un*x. Если рассматривать порты ближе - то обнаруживается, что порт - это ничто иное, как очередь сообщений, ассоциированная с пространством объектов IPC (IPC space в терминологии Mach) данной задачи и "защищаемая" специфичными правами доступа. Не каждый участник IPC может послать сообщение в порт, даже получив на него ссылку, и естественно не каждый участник может получить сообщение с любого существующего порта. Это низкоуровневой примитив IPC, жизненно важный для Mach. Механизм портов Mach довольно запутанный и эзотерический. В интернете часто сетуют на слабую и даже почти никакую документированность интерфейсов, а также на запутанность работы с сообщениями и портами. Увы, это чистая правда. Apple в лучшем случае предоставляет древние man-страницы из проекта CMU Mach, и, как правило, почти вообще никак не освещает специфичные для слоя Mach детали, хоть они и могут быть весьма важны для реализации новой функциональности даже в юзерлэнде. Сама Apple использует возможности Mach в полной мере - уберите обмен сообщениями и рухнет всё, что сделано в и для OS X, - мы убедимся в этом лишний раз на примере темы исключений, - даже при том, что OS X не является микроядерной - сервера, строго говоря, вообще не являются обязательными для неё, но порты и обмен сообщениями оказались неожиданно удобным и мощным механизмом, сфера применения которого к тому же вовсе не ограничивается одной лишь реализацией идеальной модели ОС на микроядре. Для полноты картины надо упомянуть, что ловушки Mach (mach traps), обеспечивающие механизм обмена сообщениями, являются лишь одним из классов системных сервисов (системных вызовов), обслуживаемых ядром (его Mach-сердцевиной) наряду с традиционными и более привычными системными вызовами BSD, поверх которых реализуется слой POSIX. Проект GNU/Hurd тоже не балует обилием информации, а об оригинальном проекте CMU Mach даже нечего и говорить. Он давно остановлен и то, что можно найти на ресурсах, имеющих отношение к GNU/Hurd и OS X оттуда в основном и было взято. Остаётся код ядра, который сам по себе не является хорошим источников документации, хоть и незаменим для понимания механизмов работы.</p>
<p align="justify">Итак, порт - это конечная точка однонаправленного канала коммуникации, вроде пайпа. Можно смотреть на порт, как на очередь сообщений. Порты имеют такие атрибуты, как имя и права (это является ещё одним источником терминологической путаницы - непонятно, что конкретно называют <i>именем порта</i> - право или всё же идентификатор порта - не будем заострять внимание на этой казуистике. Это просто пример неполноты информации о портах). Права не особо разнообразны. С портом может быть ассоциировано право на приём сообщений или на отправку в него (send/receive rights). Попросту, это права на извлечение/добавление сообщений в очередь сообщений и из неё. Право на приём сообщений из порта может иметь лишь одна задача. Чего-то вроде широковещательной передачи 1 ко многим здесь нет. Права на отправку сообщений в данный порт могут иметь разные задачи одновременно. Держателями портов могут быть и потоки, но пространство объектов IPC для них будет общим, если все эти потоки принадлежат одной задаче (задача или процесс, как контейнер общих ресурсов, доступных всем потокам, принадлежащим задаче).</p>
<p align="justify">Исторически обмен сообщениями в Mach - это не просто транспорт. Это нечто большее, являющееся аналогом RPC - remote procedure call, как говорилось выше. Клиент-серверная модель для Mach является весьма важной концепцией. Вспомните пример с сервером файловой системы. Сервер реализует некую подсистему, работающую по определённому протоколу. Протокол - это структура сообщений, которыми обмениваются клиент и сервер. Мы очень кратко взглянем на общую структуру сообщения чуть ниже, ведь нам придётся иметь непосредственное дело с сообщениями в коде примера обработки исключений. Пока важно сказать следующее: будь то файловая система, часы, таймеры, даже процессоры и наборы процессоров и проч., - всё это с точки зрения Mach объекты. С объектами определённого типа работает определённая подсистема, т.е., сервер. Сервисы, предоставляемые сервером, доступны через порты Mach. Имея порт объекта, путём обмена сообщениями с сервером вы можете манипулировать объектом. Так мы получаем ещё одну аналогию для порта - порт помимо прочего может рассматриваться как дескриптор объекта. Сервер вовсе не обязан быть самостоятельным процессом, выполняющимся в своём собственном адресном пространстве. Ядро Mach (а следовательно и XNU) имеет ряд встроенных подсистем. Само ядро является сервером. Впрочем, это не сильно противоречит идее процесса-сервера, потому что само ядро является задачей, хотя и не совсем обычной. Так, например, управление задачами возможно при получении так называемого порта задачи (task port). Процесс может получить свой собственный порт с помощью вызова <span class="mono">mach_task_self()</span>. Что важнее, порт данной задачи может получить и сторонний процесс. Например с помощью вызова <span class="mono">task_for_pid()</span>. Надо сказать, что возможности манипулирования задачами со стороны в Mach весьма обширны, а их результаты могут быть весьма плачевны и разрушительны для манипулируемого объекта. Поэтому операция получения порта задачи по её PID - т.е., сторонней задачи, как правило, является привилегированной. Кроме задач свои порты имеют потоки (thread port) и даже хост - как абстракция всей системы (host port). Аналогично существуют трапы <span class="mono">mach_thread_self()</span> и <span class="mono">mach_host_self().</span> Данные трапы возвращают значения типов <span class="mono">task_t</span>, <span class="mono">thread_t</span> и <span class="mono">host_t</span> соответственно, которые, однако, являются алиасами обычного типа, представляющего порт в пространстве пользователя - <span class="mono">mach_port_t</span>. К слову, о типах. Внутри ядра <span class="mono">task_t</span> является алиасом для указателя на <span class="mono">struct task</span> - дескриптора задачи Mach, довольно объёмной и безусловно важной структуры.</p>
<p align="justify">Очень быстро пробежавшись по портам, коснёмся сообщений. Сообщение частично структурировано. У него должен быть как минимум хотя бы заголовок. Но в целом, формат сообщения определяется лишь соглашением между участниками коммуникации. Минимальное сообщение на отправку выглядит так:</p>
<pre><span class="keyword">struct</span> some_msg<span class="operator"> {</span>
mach_msg_header_t head<span class="operator">;
};</span></pre>
<p align="justify">Такое сообщение несёт совсем немного информации помимо чисто служебной. А именно, в поле <span class="mono">msgh_id</span> структуры <span class="mono">mach_msg_header_t</span>. Это поле не интерпретируется ядром и может иметь любое произвольное значение. Более полную структуру заголовка можно увидеть <a href="http://fxr.watson.org/fxr/source/mach/message.h?v=MK84#L150">здесь</a>. Маловато, не правда ли? В качестве полезной нагрузки сообщение может нести предопределённые дескрипторы. Например, сами порты - как в Unix можно обмениваться файловыми дескрипторами посредством локальных Unix-сокетов, так же Mach позволяет обмен портами. Кроме того дескриптор может содержать описание OOL-данных (out-of-line) - большие куски данных, которые нельзя пропихнуть в сообщении. Это напоминает приложения к электронному письму или scatter-списки. Строго говоря, структура сообщения всё же достаточно произвольна и единственной обязательной его частью является заголовок, который должен быть первым в определяемом пользовательском типе. Ещё одно "но", специфичное для XNU, при приёме сообщения пользовательский тип должен иметь достаточный размер, чтобы содержать поле типа <span class="mono">mach_msg_trailer_t</span> - результат обработки сообщения ядром, "заказанный" получателем. Таким результатом может быть ссылка на маркер безопасности задачи-отправителя, токен аудита и тому подобное. Если со стороны отправителя минимальное сообщение состоит из одного лишь заголовка, то на стороне получателя нужно учесть место для минимального трейлера сообщения - <span class="mono">mach_msg_trailer_t</span>. Размер трейлера не учитывается в поле <span class="mono">msgh_size</span> структуры <span class="mono">mach_msg_header_t</span> (см. формат заголовка по ссылке выше). Он содержится в поле <span class="mono">mach_msg_trailer_size_t</span> структуры <a href="http://fxr.watson.org/fxr/source/osfmk/mach/message.h?v=xnu-1699.24.8#L363"><span class="mono">mach_msg_trailer_t</span></a>. Похоже, трейлеры сообщений являются частью поддержки BSM (Basic Security Module - система аудита безопасности) и в оригинальных версиях CMU Mach, равно как и в GNU Mach трейлеры не предусмотрены (т.к. и BSM там не поддерживается).</p>
<p align="justify">Наконец, API или трапы, обслуживающие обмен сообщениями и экспортируемые ядром в пространство пользователя:</p>
<table width="95%">
<col width="40%">
<col width="60%">
<tr>
<td>
<pre><span class="mono">mach_msg_return_t mach_msg
(mach_msg_header_t msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t eceive_limit,
mach_port_t receive_name,
mach_msg_timeout_t timeout,
mach_port_t notify);</span></pre>
</td>
<td>
Универсальный трап - отправить или принять сообщение. <span class="mono">msg</span> - буфер для данных сообщения. В случае получения сообщения поле <span class="mono">msgh_local_port</span> заголовка сообщения и ссылка на порт <span class="mono">receive_name</span> имеют одинаковое значение. Флаги <span class="mono">option</span> - <span class="mono">MACH_RCV_MSG</span>, <span class="mono">send_size</span> - 0, <span class="mono">receive_limit</span> - размер пользовательского сообщения (в XNU сообщение должно включать хотя бы один трейлер), <span class="mono">receive_name</span> - порт, на который у нас есть право получения сообщений, <span class="mono">timeout</span> - сколько времени ждать сообщения (<span class="mono">MACH_MSG_TIMEOUT_NONE</span> - таймаута нет, вызов блокирует пока не будет получено сообщение), <span class="mono">notify</span> - порт уведомлений. Часто <span class="mono">MACH_PORT_NULL</span>.<br /><br />
В случае отправки поле <span class="mono">msgh_local_port</span> заголовка сообщения и ссылка на порт <span class="mono">receive_name</span> равны <span class="mono">MACH_PORT_NULL</span>. <span class="mono">msg.msgh_remote_port</span> содержит ссылку на порт получателя. Флаги <span class="mono">option</span> - <span class="mono">MACH_SEND_MSG</span>, <span class="mono">send_size</span> - полный размер сообщения ( = значению поля <span class="mono">msgh_size</span> структуры <span class="mono">mach_msg_header_t</span>), <span class="mono">receive_limit</span> - 0, <span class="mono">timeout</span> - сколько времени ждать доставки (<span class="mono">MACH_MSG_TIMEOUT_NONE</span> - таймаута нет, вызов блокирует, пока сообщение не будет доставлено, т.е., добавлено в очередь сообщений принимающего процесса. Блокирование на отправке возможно тогда, когда принимающая очередь переполнена. Таким образом, пока на принимающей стороне поток не удалит сообщение из очереди, освободив в ней место, <span class="mono">mach_msg()</span> не вернёт управление, если только не задан таймаут доставки), <span class="mono">notify</span> - см. чут выше.<br /><br />
<a href="http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_msg.html">http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_msg.html</a><br /><br />
</td>
</tr>
<tr>
<td align="left" valign="top">
<pre><span class="mono">kern_return_t mach_port_allocate
(ipc_space_t task,
mach_port_right_t right,
mach_port_name_t *name);</span></pre>
</td>
<td>Создать порт заданного типа. <span class="mono">task</span> - ссылка на порт (в данном случае - пространство IPC) задачи. Обычно в качестве значения этого аргумента передаётся то, что возвращает уже знакомый нам вызов <span class="mono">mach_task_self()</span>. <span class="mono">right</span>, права для создаваемого порта. Либо <span class="mono">MACH_PORT_RIGHT_RECEIVE</span>, либо <span class="mono">MACH_PORT_RIGHT_PORT_SET</span>. С первым вроде всё достаточно ясно. Второе - набор портов. См. ниже. <span class="mono">name</span> - указатель, куда сохранить ссылку на новый порт.<br /><br />
<a href="http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_allocate.html">http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_allocate.html</a><br /><br />
</td>
</tr>
<tr>
<td align="left" valign="top">
<pre><span class="mono">kern_return_t mach_port_insert_right
(ipc_space_t task,
mach_port_name_t name,
mach_port_poly_t right,
mach_msg_type_name_ right_type);</span></pre>
</td>
<td>Добавить в порт указанный тип прав. <span class="mono">task</span> - ссылка на порт (в данном случае - пространство IPC) задачи. Обычно в качестве значения этого аргумента передаётся то, что возвращает уже знакомый нам вызов <span class="mono">mach_task_self()</span>. <span class="mono">name</span> - порт, на который у нас есть ссылка, объект манипуляции с правами.<span class="mono">right</span> - это странно, но обычно то же, что и в <span class="mono">name</span>. <span class="mono">right_type</span> - <span class="mono">MACH_MSG_TYPE_MAKE_SEND</span>, похоже, это даёт право посылки сообщений через данный порт при получении ссылки на него. <span class="mono">MACH_MSG_TYPE_MAKE_SEND_ONCE</span> or <span class="mono">MACH_MSG_TYPE_MOVE_SEND_ONCE</span> - порт используется для посылки лишь однократно. После этого он больше не может быть использован.<br /><br />
<a href="http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_allocate.html">http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_insert_right.html</a><br /><br />
</td>
</tr>
<tr>
<td align="left" valign="top">
<pre><span class="mono">kern_return_t mach_port_deallocate
(ipc_space_t task,
mach_port_name_t name);</span></pre>
</td>
<td>Уменьшить счётчик ссылок на порт (вопреки названию трапа, он вовсе не обязательно уничтожает порт. Кстати, утечка портов, как и утечка любых ресурсов в любых ОС - вещь определённо не очень хорошая). <span class="mono">task</span> - ссылка на порт (в данном случае - пространство IPC) задачи. <span class="mono">name</span> - ссылка на порт, которым мы владеем.<br /><br />
<a href="http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_deallocate.html">http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_deallocate.html</a><br /><br />
</td>
</tr>
</table>
<p align="justify">Примечательно, что порт можно создать только с правом <span class="mono">MACH_PORT_RIGHT_RECEIVE</span> или <span class="mono">MACH_PORT_RIGHT_PORT_SET</span>, вопреки ожидаемой возможности задать право и на отправку. Не в том смысле, чтобы порт имел оба права одновременно - мы помним, что порт является конечной точкой <i>однонаправленного</i> канала, а чтобы он был предназначен именно для отправки сообщений. Но с другой стороны, если предположить, что порт может быть лишь принимающей стороной (ведь очереди исходящих сообщений нет - есть только очередь входящих и порт, как мы знаем, это очередь сообщений), а то, что держит отправитель - это <i>ссылка</i> (reference) на порт, то всё вроде становится на свои места. Остаётся выяснить, как отправитель может получить ссылку на порт. В нижеследующем примере получатель "публикует" ссылку на порт, который он держит, с помощью вызова <span class="mono">thread_set_exception_ports()</span>. На этом мы и остановимся пока, не углубляясь в такие детали, как bootstrap-сервер. Пока будет достаточно отметить, что практически каждый объект ядра, с которым программист имеет дело явно или неявно, и обслуживаемый слоем Mach (потоки, задачи, но не BSD-сокеты или файлы, например, о которых слой Mach совершенно ничего не знает) представлен портом. Есть порты, которые представляют часы, хост и даже процессоры (получить порт задачи на Mach означает получить абсолютно полный контроль над ней и творить совершенно невообразимые вещи, поэтому вызов <span class="mono">task_for_pid()</span>, возвращающий порт задачи для процесса с заданным PID в OS X требует привилегий суперпользователя). Помимо этого любая задача может создавать порты сама (приблизительно как ТСР/IP-сервер может создавать сокеты и принимать на них запросы). Согласно объектно-ориентированной парадигме задача, поток, часы и т.п. - это объекты. Порт объекта - это ссылка на объект. Ссылка может позволять лишь получить общую информацию об объекте, а может открывать возможности для манипулирования объектом - так называемые порты управления (control port). Такие порты могут требовать определённых привилегий и потому называются привелигированными или специальными портами.</p>
<p align="justify">Здесь я лишь вскользь упомянул самые важные API, не особо вдаваясь в их суть и дальнейшие пояснения. Это просто подсказка для тех, кого тема заинтересовала и кто захочет поискать материал самостоятельно. Но из приведённого выше уже видно, что интерфейсы Mach определённо не забава для скучающих школьников. Тема IPC в Mach сама по себе обширна, эзотерична и заслуживает отдельной статьи (которая, быть может, когда-нибудь и появится). Этих базовых знаний нам должно хватить для рассмотрения нашего примера обработки исключений.</p>
<p align="justify">Вернёмся непосредственно к исключениям. Как следует из определения, исключение - некая нештатная ситуация, возникающая в ходе выполнения потока. Mach предлагает интересный фреймворк для работы с исключениями, но никак не определяет логику обработки исключения. Именно так. В лучших традициях Mach, исключение - это просто ещё один тип сообщений, сама "обработка" исключений - полностью основана на межпроцессном взаимодействии. Наверно было бы правильнее говорить о доставке исключений Mach, а не обработке. Исключения преобразуются в сообщения. Сообщения кто-то должен принимать. Т.е., кто-то должен создать порт и ожидать на нём поступления сообщений. Такие порты называются портами исключений. Порты исключений могут быть у каждого отдельного потока, целой задачи или общие порты для всех задач в системе - порты исключений хоста. Обычно, если поток или задача явным образом не выказывают желания заниматься собственными исключениями, их порты исключений равны <span class="mono">MACH_PORT_NULL</span>, а в общем случае задачи и потоки как раз не вовлекаются активно в работу с исключениями. Т.е., не определяют свои порты исключений. Просматривается определённая иерархия. Если некий поток вызывает исключение, но он не имеет собственного порта исключений, то исключения может обработать единый для всех потоков задачи обработчик уровня задачи. Если же и задача не предоставляет портов исключений, наступает очередь хоста. Кроме иерархичности можно заметить ещё одну интересную деталь этой модели. Исключения - это сообщения. Сообщения - средство IPC, то, чем могут между собой обмениваться потоки, в том числе принадлежащие разным задачам... Нелокальность. Вот на что я намекаю. Поток, обрабатывающий исключение, совсем не обязан делить общее адресное пространство со сбойным потоком. Он может принадлежать другой задаче, а чисто теоретически вообще работать на другом хосте (но не практически, т.к. транспорт сообщений Mach по сети в OS X не поддерживается). <i>Обработка исключения не обязана производиться в контексте потока, вызвавшего исключение.</i></p>
<p align="justify">Довольно теории. Попробуем посмотреть на то, как это происходит в реальной жизни. Предлагаю рассмотреть следующий пример и результаты его выполнения:</p>
<pre><span class="pre">#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <mach/mach.h>
</span><span class="comment">
/* exception message we will receive from the kernel */</span><span class="keyword">
typedef struct</span> exception_msg<span class="operator"> {</span>
mach_msg_header_t head<span class="operator">;</span>
mach_msg_body_t msgh_body<span class="operator">;</span><span class="comment"> /* start of kernel-processed data */</span>
mach_msg_port_descriptor_t thread<span class="operator">;</span><span class="comment"> /* victim thread */</span>
mach_msg_port_descriptor_t task<span class="operator">;</span><span class="comment"> /* end of kernel-processed data */</span>
NDR_record_t ndr<span class="operator">;</span><span class="comment"> /* see osfmk/mach/ndr.h */</span>
exception_type_t exception<span class="operator">;</span>
mach_msg_type_number_t codeCnt<span class="operator">;</span><span class="comment"> /* number of elements in code[] */</span>
exception_data_t code<span class="operator">;</span><span class="comment"> /* an array of integer_t */</span><span class="type">
char</span> pad<span class="operator"> [</span><span class="int">512</span><span class="operator">];</span><span class="comment"> /* for avoiding MACH_MSG_RCV_TOO_LARGE */</span><span class="operator">
}</span> exception_msg_t<span class="operator">;</span><span class="comment">
/* reply message we will send to the kernel */</span><span class="keyword">
typedef struct</span> reply_msg<span class="operator"> {</span>
mach_msg_header_t head<span class="operator">;</span>
NDR_record_t ndr<span class="operator">;</span><span class="comment"> /* see osfmk/mach/ndr.h */</span>
kern_return_t ret_code<span class="operator">;</span><span class="comment"> /* indicates to the kernel what to do */</span><span class="operator">
}</span> reply_msg_t<span class="operator">;</span>
mach_port_t exception_port<span class="operator">;</span><span class="type">
void</span> exception_handler<span class="operator"> (</span><span class="type">void</span><span class="operator">);</span><span class="keyword">
extern</span> boolean_t exc_server<span class="operator"> (</span>mach_msg_header_t<span class="operator"> *</span>request<span class="operator">,</span> mach_msg_header_t<span class="operator"> *</span>reply<span class="operator">);</span><span class="keyword">
typedef</span><span class="type"> void</span><span class="operator">(*</span> funcptr_t<span class="operator">)(</span><span class="type">void</span><span class="operator">);</span>
funcptr_t bad_fn<span class="operator">;</span>
kern_return_t repair_instruction<span class="operator"> (</span>mach_port_t victim<span class="operator">);</span><span class="type">
void</span> exit_gracefully<span class="operator"> (</span><span class="type">void</span><span class="operator">);</span><span class="pre">
#define L_MARGIN "%-21s: "
#define fn_puts_n(msg) printf (L_MARGIN "%s", __FUNCTION__, msg)
#define fn_puts(msg) printf (L_MARGIN "%s\n", __FUNCTION__, msg)
#define fn_puts_ids(msg) printf (L_MARGIN "%s (task %#lx, thread %#lx)\n", \
__FUNCTION__, msg, (long)mach_task_self (), (long)pthread_mach_thread_np (pthread_self ()));
#define EXIT_ON_MACH_ERROR(msg, retval) \
if (kr != KERN_SUCCESS) { mach_error(msg ":" , kr); exit((retval)); }
#define OUT_ON_MACH_ERROR(msg, retval) \
if (kr != KERN_SUCCESS) { mach_error(msg ":" , kr); goto out; }
</span><span class="type">
int</span><span class="keyword"> main</span><span class="operator"> (</span><span class="type">int</span> argc<span class="operator">,</span><span class="type"> char</span><span class="operator">**</span> argv<span class="operator">)
{</span>
kern_return_t kr<span class="operator">;</span>
pthread_t exception_thread<span class="operator">;</span>
mach_port_t this_tsk<span class="operator"> =</span> mach_task_self<span class="operator"> ();</span>
mach_port_t this_thread<span class="operator"> =</span> mach_thread_self<span class="operator"> ();</span>
fn_puts_ids<span class="operator">(</span><span class="string">"starting up"</span><span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>argc<span class="operator"> ></span><span class="int"> 1</span><span class="operator"> &&</span> strcmp<span class="operator"> (</span>argv<span class="operator"> [</span><span class="int">1</span><span class="operator">],</span><span class="string"> "mach"</span><span class="operator">) ==</span><span class="int"> 0</span><span class="operator">) {</span>
kr<span class="operator"> =</span> mach_port_allocate<span class="operator"> (</span>this_tsk<span class="operator">,</span> MACH_PORT_RIGHT_RECEIVE<span class="operator">, &</span>exception_port<span class="operator">);</span>
EXIT_ON_MACH_ERROR<span class="operator">(</span><span class="string">"mach_port_allocate"</span><span class="operator">,</span> kr<span class="operator">);</span>
kr<span class="operator"> =</span> mach_port_insert_right<span class="operator"> (</span>this_tsk<span class="operator">,</span> exception_port<span class="operator">,</span> exception_port<span class="operator">,</span> MACH_MSG_TYPE_MAKE_SEND<span class="operator">);</span>
OUT_ON_MACH_ERROR<span class="operator">(</span><span class="string">"mach_port_insert_right"</span><span class="operator">,</span> kr<span class="operator">);</span>
kr<span class="operator"> =</span> thread_set_exception_ports<span class="operator"> (</span>this_thread<span class="operator">,</span> EXC_MASK_BAD_ACCESS<span class="operator">,</span> exception_port<span class="operator">,</span> EXCEPTION_DEFAULT<span class="operator">,</span> THREAD_STATE_NONE<span class="operator">);</span>
OUT_ON_MACH_ERROR<span class="operator">(</span><span class="string">"thread_set_exception_ports"</span><span class="operator">,</span> kr<span class="operator">);</span><span class="flow">
if</span><span class="operator"> ((</span>pthread_create<span class="operator"> (&</span>exception_thread<span class="operator">, (</span>pthread_attr_t<span class="operator"> *)</span><span class="int">0</span><span class="operator">, (</span><span class="type">void</span><span class="operator"> *(*)(</span><span class="type">void</span><span class="operator"> *))</span>exception_handler<span class="operator">, (</span><span class="type">void</span><span class="operator"> *)</span><span class="int">0</span><span class="operator">))) {</span>
perror<span class="operator"> (</span><span class="string">"pthread_create"</span><span class="operator">);</span><span class="flow">
goto</span> out<span class="operator">;
}</span>
fn_puts<span class="operator">(</span><span class="string">"about to dispatch exception_handler pthread"</span><span class="operator">);</span>
pthread_detach<span class="operator">(</span>exception_thread<span class="operator">);
}</span>
fn_puts<span class="operator">(</span><span class="string">"about to call function_with_bad_instruction"</span><span class="operator">);</span>
bad_fn<span class="operator"> = (</span>funcptr_t<span class="operator">)</span>exception_thread<span class="operator">;</span>
bad_fn<span class="operator"> ();</span>
fn_puts<span class="operator">(</span><span class="string">"after function_with_bad_instruction"</span><span class="operator">);</span>
out<span class="operator">:</span><span class="comment">
/* not reached if no mach exception handler registered */</span>
mach_port_deallocate<span class="operator"> (</span>this_tsk<span class="operator">,</span> this_thread<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>exception_port<span class="operator">)</span>
mach_port_deallocate<span class="operator"> (</span>this_tsk<span class="operator">,</span> exception_port<span class="operator">);</span><span class="flow">
return</span><span class="int"> 0</span><span class="operator">;
}</span><span class="type">
void</span> exception_handler<span class="operator"> (</span><span class="type">void</span><span class="operator">)
{</span>
kern_return_t kr<span class="operator">;</span>
exception_msg_t msg_recv<span class="operator">;</span>
reply_msg_t msg_resp<span class="operator">;</span>
fn_puts_ids<span class="operator">(</span><span class="string">"beginning"</span><span class="operator">);</span>
msg_recv<span class="operator">.</span>head<span class="operator">.</span>msgh_local_port<span class="operator"> =</span> exception_port<span class="operator">;</span>
msg_recv<span class="operator">.</span>head<span class="operator">.</span>msgh_size<span class="operator"> =</span><span class="keyword"> sizeof</span><span class="operator">(</span>msg_recv<span class="operator">);</span>
kr<span class="operator"> =</span> mach_msg<span class="operator"> (&(</span>msg_recv<span class="operator">.</span>head<span class="operator">),</span> MACH_RCV_MSG<span class="operator"> |</span> MACH_RCV_LARGE<span class="operator">,</span><span class="int"> 0</span><span class="operator">,</span><span class="keyword"> sizeof</span><span class="operator">(</span>msg_recv<span class="operator">),</span> exception_port<span class="operator">,</span>
MACH_MSG_TIMEOUT_NONE<span class="operator">,</span> MACH_PORT_NULL<span class="operator">);</span>
EXIT_ON_MACH_ERROR<span class="operator">(</span><span class="string">"mach_msg_receive"</span><span class="operator">,</span> kr<span class="operator">);</span>
fn_puts<span class="operator">(</span><span class="string">"received message"</span><span class="operator">);</span>
fn_puts_n<span class="operator">(</span><span class="string">"victim thread is "</span><span class="operator">);</span>
printf<span class="operator"> (</span><span class="string">"%#lx\n"</span><span class="operator">, (</span><span class="type">long</span><span class="operator">)</span>msg_recv<span class="operator">.</span>thread<span class="operator">.</span>name<span class="operator">);</span>
fn_puts_n<span class="operator">(</span><span class="string">"victim thread's task is "</span><span class="operator">);</span>
printf<span class="operator"> (</span><span class="string">"%#lx\n"</span><span class="operator">, (</span><span class="type">long</span><span class="operator">)</span>msg_recv<span class="operator">.</span>task<span class="operator">.</span>name<span class="operator">);</span>
fn_puts<span class="operator">(</span><span class="string">"calling exc_server"</span><span class="operator">);</span>
exc_server<span class="operator"> (&</span>msg_recv<span class="operator">.</span>head<span class="operator">, &</span>msg_resp<span class="operator">.</span>head<span class="operator">);</span>
fn_puts<span class="operator">(</span><span class="string">"sending reply"</span><span class="operator">);</span>
kr<span class="operator"> =</span> mach_msg<span class="operator"> (&(</span>msg_resp<span class="operator">.</span>head<span class="operator">),</span> MACH_SEND_MSG<span class="operator">,</span> msg_resp<span class="operator">.</span>head<span class="operator">.</span>msgh_size<span class="operator">,</span><span class="int"> 0</span><span class="operator">,</span> MACH_PORT_NULL<span class="operator">,</span>
MACH_MSG_TIMEOUT_NONE<span class="operator">,</span> MACH_PORT_NULL<span class="operator">);</span>
EXIT_ON_MACH_ERROR<span class="operator">(</span><span class="string">"mach_msg_send"</span><span class="operator">,</span> kr<span class="operator">);</span>
pthread_exit<span class="operator"> ((</span><span class="type">void</span><span class="operator"> *)</span><span class="int">0</span><span class="operator">);
}</span>
kern_return_t catch_exception_raise<span class="operator"> (</span>mach_port_t port<span class="operator">,</span> mach_port_t victim<span class="operator">,</span> mach_port_t task<span class="operator">,</span>
exception_type_t exception<span class="operator">,</span> exception_data_t code<span class="operator">,</span> mach_msg_type_number_t code_count<span class="operator">)
{</span>
fn_puts_ids<span class="operator">(</span><span class="string">"beginning"</span><span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>exception<span class="operator"> !=</span> EXC_BAD_ACCESS<span class="operator">) {</span>
fn_puts<span class="operator">(</span><span class="string">"unexpected exception!\n"</span><span class="operator">);</span>
exit<span class="operator"> (-</span><span class="int">1</span><span class="operator">);
}</span><span class="flow">
return</span> repair_instruction<span class="operator"> (</span>victim<span class="operator">);
}</span><span class="pre">
#define GPR(p,pp, n) \
state.uts.ts ## p.__ ## pp ## n
#define GPR64(n) GPR(64, r, n)
#define GPR32(n) GPR(32, e, n)
</span><span class="type">
void</span> dump_registers<span class="operator"> (</span>x86_thread_state_t state<span class="operator">)
{</span><span class="flow">
if</span><span class="operator"> (</span>state<span class="operator">.</span>tsh<span class="operator">.</span>flavor<span class="operator"> ==</span><span class="int"> 4</span><span class="operator">) {</span>
printf<span class="operator"> (</span><span class="string">"\trip: 0x%016x, rax: 0x%016x, rbx: 0x%016x, rcx: 0x%016x, rdx: 0x%016x\n"</span><span class="operator">,</span>
GPR64<span class="operator">(</span>ip<span class="operator">),</span> GPR64<span class="operator">(</span>ax<span class="operator">),</span> GPR64<span class="operator">(</span>bx<span class="operator">),</span> GPR64<span class="operator">(</span>cx<span class="operator">),</span> GPR64<span class="operator">(</span>dx<span class="operator">));</span>
printf<span class="operator"> (</span><span class="string">"\trsp: 0x%016x, rbp: 0x%016x, rdi: 0x%016x, rsi: 0x%016x\n"</span><span class="operator">,</span>
GPR64<span class="operator">(</span>sp<span class="operator">),</span> GPR64<span class="operator">(</span>bp<span class="operator">),</span> GPR64<span class="operator">(</span>di<span class="operator">),</span> GPR64<span class="operator">(</span>si<span class="operator">));
}</span><span class="flow"> else</span><span class="operator"> {</span>
printf<span class="operator"> (</span><span class="string">"\teip: 0x%08x, eax: 0x%08x, ebx: 0x%08x, ecx: 0x%08x, edx: 0x%08x\n"</span><span class="operator">,</span>
GPR32<span class="operator">(</span>ip<span class="operator">),</span> GPR32<span class="operator">(</span>ax<span class="operator">),</span> GPR32<span class="operator">(</span>bx<span class="operator">),</span> GPR32<span class="operator">(</span>cx<span class="operator">),</span> GPR32<span class="operator">(</span>dx<span class="operator">));</span>
printf<span class="operator"> (</span><span class="string">"\tesp: 0x%08x, ebp: 0x%08x, edi: 0x%08x, esi: 0x%08x\n"</span><span class="operator">,</span>
GPR32<span class="operator">(</span>sp<span class="operator">),</span> GPR32<span class="operator">(</span>bp<span class="operator">),</span> GPR32<span class="operator">(</span>di<span class="operator">),</span> GPR32<span class="operator">(</span>si<span class="operator">));
}
}</span>
kern_return_t repair_instruction<span class="operator"> (</span>mach_port_t victim<span class="operator">)
{</span>
kern_return_t kr<span class="operator">;</span><span class="type">
unsigned int</span> count<span class="operator">;</span>
x86_thread_state_t state<span class="operator">;</span>
fn_puts_ids<span class="operator">(</span><span class="string">"fixing instruction"</span><span class="operator">);</span>
count<span class="operator"> =</span> MACHINE_THREAD_STATE_COUNT<span class="operator">;</span>
kr<span class="operator"> =</span> thread_get_state<span class="operator"> (</span>victim<span class="operator">,</span> MACHINE_THREAD_STATE<span class="operator">, (</span>thread_state_t<span class="operator">)&</span>state<span class="operator">, &</span>count<span class="operator">);</span>
EXIT_ON_MACH_ERROR<span class="operator">(</span><span class="string">"thread_get_state"</span><span class="operator">,</span> kr<span class="operator">);</span>
dump_registers<span class="operator"> (</span>state<span class="operator">);</span><span class="flow">
if</span><span class="operator"> (</span>state<span class="operator">.</span>tsh<span class="operator">.</span>flavor<span class="operator"> ==</span><span class="int"> 4</span><span class="operator">)</span>
state<span class="operator">.</span>uts<span class="operator">.</span>ts64<span class="operator">.</span>__rip<span class="operator"> = (</span>vm_address_t<span class="operator">)</span>exit_gracefully<span class="operator">;</span><span class="flow">
else</span>
state<span class="operator">.</span>uts<span class="operator">.</span>ts32<span class="operator">.</span>__eip<span class="operator"> = (</span>vm_address_t<span class="operator">)</span>exit_gracefully<span class="operator">;</span>
kr<span class="operator"> =</span> thread_set_state<span class="operator"> (</span>victim<span class="operator">,</span> MACHINE_THREAD_STATE<span class="operator">, (</span>thread_state_t<span class="operator">)&</span>state<span class="operator">,</span> MACHINE_THREAD_STATE_COUNT<span class="operator">);</span>
EXIT_ON_MACH_ERROR<span class="operator">(</span><span class="string">"thread_set_state"</span><span class="operator">,</span> kr<span class="operator">);</span>
fn_puts<span class="operator">(</span><span class="string">"after setting thread state"</span><span class="operator">);</span>
dump_registers<span class="operator"> (</span>state<span class="operator">);</span><span class="flow">
return</span> KERN_SUCCESS<span class="operator">;
}</span><span class="type">
void</span> exit_gracefully<span class="operator"> (</span><span class="type">void</span><span class="operator">)
{</span>
fn_puts_ids<span class="operator">(</span><span class="string">"dying graceful death"</span><span class="operator">);
}</span>
</pre>
<p align="justify">Собираем, запускаем на Leopard без аргументов.</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ gcc -o exh exh.c
$ ./exh
main : starting up (task 0x807, thread 0x10b)
main : about to call function_with_bad_instruction
Bus error</pre></span>
<p align="justify">Отхватили сигнал. Как и ожидалось. Теперь попробуем с аргументом:</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
$ ./exh mach
main : starting up (task 0x807, thread 0x10b)
main : about to dispatch exception_handler pthread
exception_handler : beginning (task 0x807, thread 0xf03)
main : about to call function_with_bad_instruction
exception_handler : received message
exception_handler : victim thread is 0x10b
exception_handler : victim thread's task is 0x807
exception_handler : calling exc_server
catch_exception_raise: beginning (task 0x807, thread 0xf03)
repair_instruction : fixing instruction (task 0x807, thread 0xf03)
eip: 0xb008100c, eax: 0xb0080fff, ebx: 0x00002358, ecx: 0x00000000, edx: 0xb0081000
esp: 0xbffffa55, ebp: 0xbffffab8, edi: 0x00002cc1, esi: 0xbffffb5f
repair_instruction : after setting thread state
eip: 0x00002c26, eax: 0xb0080fff, ebx: 0x00002358, ecx: 0x00000000, edx: 0xb0081000
esp: 0xbffffa55, ebp: 0xbffffab8, edi: 0x00002cc1, esi: 0xbffffb5f
exception_handler : sending reply
exit_gracefully : dying graceful death (task 0x807, thread 0x10b)</pre></span>
<p align="justify">Snow Leopard. Без аргумента то же самое, поэтому соответствующий фрагмент опущен. Сразу пробуем exh mach:</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>main : starting up (task 0x807, thread 0x903)
...
repair_instruction : after setting thread state
eip: 0x00002aa7, eax: 0xb0081000, ebx: 0x000021d2, ecx: 0x00000001, edx: 0xb0081000
esp: 0xbffffabc, ebp: 0xbffffaf8, edi: 0x00000000, esi: 0x00000000
exception_handler : sending reply
exit_gracefully : dying graceful death (task 0x807, thread 0x903)
main : after function_with_bad_instruction</pre></span>
<p align="justify">И, наконец, Lion:</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ ./exh mach
main : starting up (task 0x907, thread 0x707)
main : about to dispatch exception_handler pthread
main : about to call function_with_bad_instruction
exception_handler : beginning (task 0x907, thread 0x1303)
exception_handler : received message
exception_handler : victim thread is 0x707
exception_handler : victim thread's task is 0x907
exception_handler : calling exc_server
catch_exception_raise: beginning (task 0x907, thread 0x1303)
repair_instruction : fixing instruction (task 0x907, thread 0x1303)
rip: 0x0000000001b81000, rax: 0x0000000001b81000, rbx: 0x0000000000000000, rcx: 0x00000000019bf0f8, rdx: 0x0000000000000000
rsp: 0x00000000615bcb18, rbp: 0x00000000615bcba0, rdi: 0x00000000786742a0, rsi: 0x0000000000000303
repair_instruction : after setting thread state
rip: 0x00000000019be900, rax: 0x0000000001b81000, rbx: 0x0000000000000000, rcx: 0x00000000019bf0f8, rdx: 0x0000000000000000
rsp: 0x00000000615bcb18, rbp: 0x00000000615bcba0, rdi: 0x00000000786742a0, rsi: 0x0000000000000303
exception_handler : sending reply
exit_gracefully : dying graceful death (task 0x907, thread 0x707)
main : after function_with_bad_instruction</pre></span>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ ./exh
main : starting up (task 0x907, thread 0x707)
main : about to call function_with_bad_instruction
Segmentation fault: 11</pre></span>
<p align="justify">Как видно из происходящего и из самого исходного кода, подготовка к обработке исключений в Mach-стиле происходит только тогда, когда наша программа вызвана с аргументом "mach". В противном случае ничего такого не происходит. Почему-то на Leopard (10.5.5) дело до сообщения "after function_with_bad_instruction" не доходит. Процесс повисает. Подозреваю, из-за проблем со стеком при возврате из <span class="mono">exit_gracefully()</span>. Впрочем, для нашего случая это особого значения не имеет. Главное - пруф концепции. Обращает на себя ещё одна небольшая мелочь. В Snow Leopard и Leopard процесс получает SIGBUS, в то время как на Lion - 11, SIGSEGV. Но опять же, для нас это особого значения не имеет, так как основной целью было продемонстрировать, что без назначенного обработчика исключений Mach процесс не имеет шансов выжить, не получив сигнал.</p>
<p align="justify">Что происходит в нашей программе? Если не задан аргумент командной строки mach, то всё очень просто. Указатель на функцию bad_fn содержит NULL или случайный мусор. Управление быстро доходит до вызова bad_fn, происходит нарушение доступа, как следствие, программа получает сигнал. Всё происходит в рамках семантики Unix.</p>
<p align="justify">В противном же случае, если задан аргумент mach, мы подготавливаем обработчик исключения. Для этого создаём порт с помощью уже известного нам трапа <span class="mono">mach_port_allocate()</span>, добавляем право на отправку сообщений в порт с помощью <span class="mono">mach_port_insert_right()</span> и, очень важный шаг - назначаем созданный порт портом исключений с помощью трапа <span class="mono">thread_set_exception_ports()</span>. На этом вызове мы сейчас остановимся чуть подробнее, так как это важный момент.</p>
<p align="justify">Вызов <a href="http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/thread_set_exception_ports.html">thread_set_exception_ports()</a> имеет следующую сигнатуру:
<pre><span class="mono">kern_return_t thread_set_exception_ports
(thread_act_t thread,
exception_mask_t exception_types,
mach_port_t exception_port,
exception_behavior_t behavior,
thread_state_flavor_t flavor);</span></pre>
Здесь <span class="mono">thread</span> - порт потока, для которого мы назначаем обработчик исключения. <span class="mono">exception_types</span> - маска исключений, которые будут обработаны на данном порту. Маска соответствует исключению или комбинации исключений и напрямую соответствует типам исключений, обрабатываемым Mach. Создатели Mach определили категории исключений, не зависящих от платформы, что, в общем-то, вполне здраво и разумно. Отказы защиты памяти, арифметические ошибки - всё это в равной степени может иметь место и на платформах PowerPC, и на i386/x86-64. Вот группы исключений, с которыми работает XNU (приведены не полностью; также заметим, что общими для Mach в целом являются также не все категории из этого списка - общих категорий ещё меньше):<br /><br />
<table width="70%" style="margin:0px auto;">
<col width="25%">
<col width="70%">
<tr>
<td><span class="mono">EXC_BAD_ACCESS</span></td>
<td>Нарушение доступа к памяти<br />
маска EXC_MASK_BAD_ACCESS<br />
сигнал Unix: SIGBUS, SIGSEGV</td>
</tr>
<tr>
<td><span class="mono">EXC_BAD_INSTRUCTION</span></td>
<td>Неизвестная или неверная инструкция/опкод<br />
маска EXC_MASK_BAD_INSTRUCTION<br />
сигнал Unix: SIGILL</td>
</tr>
<tr>
<td><span class="mono">EXC_ARITHMETIC</span></td><td>Арифметическое исключение<br />
маска EXC_MASK_ARITHMETIC<br />
сигнал Unix: SIGFPE</td>
</tr>
<tr>
<td><span class="mono">EXC_EMULATION</span></td><td>Эмуляция инструкций другой архитектуры<br />
маска EXC_MASK_EMULATION<br />
сигнал Unix: SIGEMT</td>
</tr>
<tr>
<td><span class="mono">EXC_SOFTWARE</span></td><td>Программное исключение<br />
маска EXC_MASK_SOFTWARE<br />
сигнал Unix: SIGSYS, SIGPIPE, SIGABRT, SIGKILL</td>
</tr>
<tr>
<td><span class="mono">EXC_BREAKPOINT</span></td><td>Точка останова<br />
маска EXC_MASK_BREAKPOINT<br />
сигнал Unix: SIGTRAP</td>
</tr>
<tr>
<td><span class="mono">EXC_CRASH</span></td><td>Крах задачи, аварийное завершение<br />
маска EXC_MASK_CRASH</td>
</tr>
<tr>
<td><span class="mono">EXC_RESOURCE</span></td><td>Превышение лимита на ресурс<br />
маска EXC_MASK_RESOURCE</td>
</tr>
</table><br />
Кроме упомянутых, можно задать <span class="mono">EXC_MASK_ALL</span> - все категории исключений. <span class="mono">exception_port</span> - Mach-порт, созданный ранее, с правом на отправку сообщений, на котором будут приниматься сообщения об исключениях данного типа (или все, в зависимости от маски). Полный список категорий исключений и их масок можно получить их заголовка osfmk/mach/exception_types.h для текущей версии XNU. Конкретное исключение уточняется кодом, передаваемым в сообщении об исключении. В таблице приведены соответствия между категориями и сигналами Unix. При обработке исключения в ядре категория исключения преобразуется в сигнал Unix функцией <span class="mono">ux_exception()</span> из bsd/uxkern/ux_exception.c. Здесь, разумеется, речь уже исключительно о XNU, ведь другие Mach не симулируют поведение BSD на уровне ядра. Также интересно, что конкретно в XNU исключения каждой категории могут быть обслужены на своём собственном порту. Т.е., для обработки каждой категории можно завести по отдельному порту и потоку. Следующий аргумент <span class="mono">thread_set_exception_ports()</span>, <span class="mono">behavior</span> - определяет, какая информация должна быть передана обработчику исключения и, собственно, какой обработчик должен быть вызван:<br /><br />
<table width="70%" style="margin:0px auto;">
<col width="25%">
<col width="70%">
<tr>
<td><span class="mono">EXCEPTION_DEFAULT</span></td>
<td>Обработчик получает идентификатор потока<br /><br />
Функция, реализующая обработчик исключения: <span class="mono">catch_exception_raise()</span><br /></td>
</tr>
<tr>
<td><span class="mono">EXCEPTION_STATE</span></td>
<td>Обработчику передаётся состояние сбойного потока<br /><br />
Функция, реализующая обработчик исключения: <span class="mono">catch_exception_raise_state()</span><br /></td>
</tr>
<td><span class="mono">EXCEPTION_STATE_IDENTITY</span></td>
<td><span class="mono">EXCEPTION_DEFAULT</span> + <span class="mono">EXCEPTION_STATE</span><br /><br />
Функция, реализующая обработчик исключения: <span class="mono">catch_exception_raise_state_identity()</span><br /><br /></td>
</table>
На уровне хоста эти кэтчеры, кроме самого первого, являются заглушками, возвращающими <span class="mono">KERN_INVALID_ARGUMENT</span>. Итак, если на уровне задачи или потока никто не выразил готовности обработать исключение, оно попадает на обработку функции <span class="mono">catch_mach_exception_raise()</span> в bsd/uxkern/ux_exception.c - обработчику уровня хоста, т.е., ядру. Имя <span class="mono">catch_mach_exception_raise()</span> не является опечаткой. Определён и кэтчер <span class="mono">catch_exception_raise()</span>, который вызывает <span class="mono">catch_mach_exception_raise()</span>. Идентификаторы, содержащие в имени "_mach_", - 64-битные версии кэтчеров внутри ядра. Здесь решается окончательная судьба процесса, которому принадлежит сбойный поток или потока. Исключение Mach преобразуется в сигнал Unix, который доставляется адресату. Наконец,последний аргумент <span class="mono">thread_set_exception_ports()</span>, <span class="mono">flavor</span> - "разновидность" контекста потока, который нужно сообщить обработчику (кэтчеру). Учитывая то, что на уровне ядра реализован только один кэтчер (и, судя по всему, также обстоят дела в юзерланде, что уже интереснее; такои образом <span class="mono">EXCEPTION_STATE</span> и <span class="mono">EXCEPTION_STATE_IDENTITY</span> являются нонсенс-флагами), этот аргумент особого смысла не несёт. Теоретически эта разновидность может относиться к PPC, i386 или x86-64.</p>
<p align="justify">Наконец, когда создан и назначен порт, мы может запустить отдельный поток (<span class="mono">exception_handler()</span> в нашем коде), который и должен будет наше исключение обработать. Поток не делает ничего особенного. Он сразу же засыпает на порту (вызов <span class="mono">mach_msg()</span> без таймаута блокирует до тех пор, пока не будет получено хоть одно сообщение). Получив сообщение об исключении, поток вызывает <span class="mono">exc_server()</span> - этот вызов является "рабочим телом" сервера исключений. В нашем случае, сервер исключений - наш поток, но нам не нужно делать всю чёрную работу по обработке сообщений, подготовке ответа и всего такого. Для этого есть уже готовый код, который и скрывается за вызовом <span class="mono">exc_server()</span>. <span class="mono">exc_server()</span> на основе данных, которые ему передали, решает, какую функцию, кэтчер, ему нужно вызвать. И опять-таки, странное дело, но если верить своим глазам и <a href="http://fxr.watson.org/fxr/source/libsyscall/mach/exc_catcher.c?v=xnu-1699.24.8">коду из libsyscall</a>, то выбор не особо велик, что ещё раз подтверждает бпрактическую бесполезность флагов <span class="mono">EXCEPTION_STATE</span> и <span class="mono">EXCEPTION_STATE_IDENTITY</span> при вызове <span class="mono">thread_set_exception_ports()</span>. Почему так, автору совершенно непонятно и неизвестно. Кстати, упомянутый кусочек кода разоблачает чёрную магию, как система узнаёт, какой кэтчер нужно вызвать. Рантайм резолвит символ в исполняемом образе приложения и вызывает его, если оный определён. В противном случае, если кэтчер с именем <span class="mono">catch_exception_raise()</span> не определён, обработка исключения неизбежно породит новое исключение.</p>
<p align="justify">Мы уже знаем, что исключение Mach - ещё одно сообщение. Кстати, вот как оно выглядит:</p>
<pre><span class="keyword">typedef struct</span> exception_msg<span class="operator"> {</span>
mach_msg_header_t head<span class="operator">;</span>
mach_msg_body_t msgh_body<span class="operator">;</span>
mach_msg_port_descriptor_t thread<span class="operator">;</span>
mach_msg_port_descriptor_t task<span class="operator">;</span>
NDR_record_t ndr<span class="operator">;</span>
exception_type_t exception<span class="operator">;</span>
mach_msg_type_number_t codeCnt<span class="operator">;</span>
exception_data_t code<span class="operator">;</span><span class="type">
char</span> pad<span class="operator"> [</span><span class="int">512</span><span class="operator">];
}</span> exception_msg_t<span class="operator">;</span></pre>
<p align="justify">Поле <span class="mono">exception</span> содержит код категории исключения. На самом деле, название поля может быть каким угодно. Оно не играет никакой роли, пока соблюдается структура сообщения. Поля <span class="mono">thread</span> и <span class="mono">task</span> - порты потока, в котором произошло исключение, и задачи, которой принадлежит поток. <span class="mono">code</span> - дополнительные данные об исключении, массив целых чисел, ну а <span class="mono">codeCnt</span> - их общий размер. Паддинг необходим для того, чтобы зарезервировать достаточно места при приёме сообщения - ведь размер может варьировать в зависмости от размера кодовой части. Иначе есть риск получить ошибку msg too large.</p>
<p align="justify">Таким образом, <span class="mono">exc_server()</span> "распарсив" сообщение, полученное потоком, вызывает кэтчер (который проверяет тип исключения и вызывает функцию <span class="mono">repair_instruction()</span>, слегка корректирующую контекст сбойного потока - с помощью перезаписи регистра [er]ip заставляет его перескочить на функцию <span class="mono">exit_gracefully()</span>, "забыв" о злополучной <span class="mono">bad_fn</span>).</p>
<p align="justify">Мы должны отчитаться перед ядром о проделанной работе, чтобы последнее знало, что делать дальше. Обработчик должен уведомить ядро сообщением такого вида:</p>
<pre><span class="comment">/* reply message we will send to the kernel */</span><span class="keyword">
typedef struct</span> reply_msg<span class="operator"> {</span>
mach_msg_header_t head<span class="operator">;</span>
NDR_record_t ndr<span class="operator">;</span><span class="comment"> /* see osfmk/mach/ndr.h */</span>
kern_return_t ret_code<span class="operator">;</span><span class="comment"> /* indicates to the kernel what to do */</span><span class="operator">
}</span> reply_msg_t<span class="operator">;</span></pre>
<p align="justify">Это сообщение подготовлено кодом <span class="mono">exc_server()</span> и отослано нашим потоком с помощью <span class="mono">mach_msg()</span>. <span class="mono">ret_code</span> содержит код обработки исключения. <span class="mono">KERN_SUCCESS</span> уведомляет ядро о том, что исключение обработано и инцидент исчерпан.</p>
<p align="justify">Мы выяснили, как выглядят исключения в Mach на примере XNU и я уже не буду ещё раз описывать весь протокол обработки - он должен быть понятен из того, что уже написано выше. Единственное, чего не видно из примера - последовательного перебора портов от потока до хоста. Тут Вам придётся либо поверить мне на слово, либо самостоятельно ознакомиться с API <span class="mono">thread_set_exception_ports()</span> и поэкспериментировать с ним. Порты исключений наследуются процессами. Предком всех пользовательских процессов в системе в OS X является launchd. launchd оределяет обработчики исключений, на которые и происходит пересылка исключений в случае сбоя дочернего процесса. Мы также увидели, как API Mach позволяют, например, получить контекст потока. И, поверьте, это далеко не предел. Помните о великой силе портов в Mach? Собственно, на этом всём и построен, например, Crash Reporter в OS X - ведь поток, обрабатывающий исключение, может находиться и в другом процессе. Конечно же, механизм сбора данных об аварийных ситуациях в процессах можно реализовать и иначе, безо всяких там сообщений. Но всё-таки модель IPC в стиле Mach делает процесс более естественным и гибким. Надеюсь, мне удалось более или менее раскрыть эту интересную тему. Конечно, многое осталось за кадром. К сожалению, объяснить всё до последней точки я бы не смог в том объёме, в котором я сам знаком с темой, в котором мне не лень писать и на который вообще хватает времени. Поэтому за деталями я отсылаю Вас к упомянутым книгам [1] и [2] и вообще, к источникам. Обе книги, кстати, весьма полезны в плане помощи при исследовании внутренностей OS X.</p>
<h4>Источники</h4>
<p>
[1] "Mac OS X Internals: A Systems Approach" By Amit Singh<br />
[2] "Mac OS X and iOS Internals: To the Apple's Core" By Jonathan Levin<br />
[3] <a href="http://www.draga.com/~jwise/mach/documentation/unsorted/cmu/unpublished/exception.pdf">"The Mach Exception Handling Facility" David L. Black, David B. Golub, Karl Hauth, Avadis Tevanian1, and Richard Sanzi</a><br />
[4] Разнообразные исходные коды как XNU, так и GNU Mach, GNU/Hurd, CMU Mach K83/84, и ещё куча всякого хлама по всей сети.<br />
[5] <a href="http://robert.sesek.com/thoughts/2012/1/debugging_mach_ports.html">http://robert.sesek.com/thoughts/2012/1/debugging_mach_ports.html</a>
</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-949627715413091452012-09-27T07:38:00.002-07:002012-10-01T10:35:19.430-07:00Абстрактные типы данных - Бинарные деревья поиска 2<a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036" name="bst2_top"></a><br />
<table><tbody>
<tr><td><a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036#bst2_section0">Дерамиды (декартовы деревья)</a></td></tr>
<tr><td><a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036#bst2_section1">Вставка в декартово дерево</a></td></tr>
<tr><td><a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036#bst2_section2">Удаление из декартова дерева</a></td></tr>
<tr><td><a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036#bst2_section3">Заключение</a></td></tr>
</tbody></table>
<div align="justify">
Из предыдущей статьи "Абстрактные типы данных - Бинарные деревья поиска" Вы узнали, что бинарные деревья поиска являются мощной структурой данных для поиска и выборки данных. Бинарные деревья поиска могут быть довольно простыми, как и операции на них. Над бинарными деревьями возможны и более сложные операции. Однако, реализация более сложных операций не всегда так проста. В особенности, когда речь идёт о вращениях и прочих структурных преобразованиях дерева. Я также указывал уже на то, что в некоторых определённых случаях базовые алгоритмы на бинарных деревьях могут быть крайне неэфективны. Можно выделить три наихудших случая: данные, поступающие на вход, упорядочены по возрастанию:</div>
<div align="center">
<img alt="bst2_0" src="data:image/gif;base64,R0lGODlhhQCcAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///ywAAAAAhQCcAAAI/wAfCBxIsKDBgwgTKlzIsKHDhxAXApgIIKLFixgzakRIseLAjhtDihyZcSJDkyRTqlz5wKNDlyxjynwI8+XMmzgP1qSZs+fNnRCB+hy6UahNokhDGuWZtKnFpUGdSj2qdKpVhVCZXt1KMGvWllzDgpU4lqzYq17LYj2L9qTahF/Z/jT7VqfcqVBdpr0r1WhHimv59oVbcGlcwTGFAvWL2KliuxwbO64qeXLJyoMvY7b8dDPezp4/aw3d1+th0jhBfgRcFzVe1oVdV/arWjbn1TVh2yYKM+/upBVRSjz9WyRx4sU1R0SeHPTy5jOZC5QO3S1G6tUJa8Se3SB37t27cv/8qz180fLT0Zu/yFh85PXKvcd+D589XPLq64+e/zG//obt9Ufff1FBJqB8BDrHX3oGJrgfbrot6CBVCk5I4XMWYqhhhg8CyGGF1n243FfCiQjifOBlGCFsKX6In4QmrtRijAOOVyKNHnqnW204BjadaT36eNqM8AV3YpAMHtkjdkR2xySS7m0HZWv2IZnXjQ3iaNiBNdIYYJJZxrhlgVr6eKGJY3boopk5lqmeb0GmCWecw+1Fp3FQtthkc+DtCd2TU4KppJuDLlnonWcGutpJRipqY287OlrnY5JKWWl8j14KY0s7RqhobkBWqheZoh7q5XWSAhqoqnlSZuVIfv61pmerOnrapZgNkvhqlroaymubp/4aYrAIcunfmsLSRWiUgoaJa7FUboqmTjyySex5tFo6JavZVvkpqqVumCqpl2KpY7SfdlrileaiaWtrPFbrKKSMaurRkPYiF2t20u3Lp7bbAryrwMsS/KyrvlL7orPT3nossg4/rKK1yh6ssLzScphmohAzLPHEEXvcccYia3zfwtAWjKnKppq8MqLergruuOLOS66ooWralbr+OoixziQFBAA7" /></div>
<div align="justify">
Добавляемые данные отсортированы в убывающем порядке:</div>
<div align="center">
<img alt="bst2_1" src="data:image/gif;base64,R0lGODlhhQCcAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///ywAAAAAhQCcAAAI/wAfCBxIsKDBgwgTKlzIsKHDhxAjSpxIsSJEABgBWNzIsaPHghk1Dgz5saTJkyBFKsSIsqVLiiobxnxJsyZIiTNt6myZ02HPnUA5/vQZtKjHoTKNKq2IlOjSp06FQp26MmHIjFWpar2JsCnXrWCHeiU4FuxOpGVHmtWKNmLatS/RXm36Fi7PrA/r2j3Z9uJeqGL9/n0qdi7ewUb1dkW8VPFBx4w7QlYbubHFyZWlMs1MGCZnwnoxf74s8K3o0Z5HemWJmi/Wx49fl5bdWjJtkpSt0q5tmyHL07xxCg4ONDTxs6mPxyWt3OVk4M0NioYevfTR6h97Xo2KfaN2skm7a//+an0h9eY/Z449fzw9eavimUt/vzh+8vfr7d8Hz9+8/v3lBXjYf8ORhZtvBE4EHHvKLZigguM96JZ8Ek64WYUQZoihhnltKJyACHpY4AOsrcRgd9rdtpuI4ZlYIosWwsjUiiDK6JOKL54Y34tdaaSjeKbZOGKLQg7YYZHwXYjkfEoumVuTSzroJInXTUkXj7BZiRddWib5ZJZI9nVkmEb6F2WZaLIoJndFBtYfmGd6yWWXuq02JZWS3YknhXRyeOdzer45pJbGBRpgkIYKiKV0P0aXYo40/ulbpIE2qt92TCbKqHolWgqkmZ5WtyijmtJnZqmKhdqen3pCpipvria+mh6mcPYpqHuV1vflrmTqWiOvQhZ2YK291uqmrbd66eSxvzZrI7N7ZorsbMOaWiyAcUI5LZuSYrvtqbKymuugrZK43qvExTRnqdFSmxK6q/ZIqYzVOmvosLjB295q+qZLbrkxhivusnx+S6TB3LZZJcFpWktvVfkZ7OPBwfpXVr+DiRnxtZlurLC89Upbsa/tEvusyWty/GvKH0eosrcne3dvwQzD/DLFAP/bbcKy8stubJCO+rOBQg9ttEIBAQA7" /></div>
<div align="justify">
Или, наконец, чередующийся порядок, задом наперёд:</div>
<div align="center">
<img alt="bst2_2" src="data:image/gif;base64,R0lGODlhQQBzAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///ywAAAAAQQBzAAAI/wAfCBxIsOADAAgBGFzIsKHDhwYTKhwoEaLFixARZpyIsSNGjhZBehy5UGRIkigLmry4MiVLly1dPox5UmZHmi9t1rSJU+dBnz119gwalOfMn0d9NhyKNKnSiBmbOiyaEidHpk+hTqUYNavKqRI1bvX6laFImlRhmi25lKzWtwRjpq0K1C1clHMrjpyrkmjCuH9z4pUacWhRvgKvrv0Yku/ZuzudRuZKeS9Lw20Je6SqN3HgrYgr3/wsmK7duHVPJzYb1qrq1WxvvtYMm7FquZZPy23d1S7uzbczy9Yt3Dbx2MaPFw4reaPY5Gpnku481uhSzM1Joh1+Pfpi7n1pQ/9HrT28aPBlB9em6Lh6+eeQ3ccHLn2jfNPS4VP/LjMv6d7L/TdfVr+dl55v8rmmHHLrMUgWWsxlR2CCEj61HXnFuVVggwciyJ94HD543X4DWpjaa6FhOFuKIGro3WwhTtYYfJs5dth0Aso4llXYjWafgUBmeJl+Oa7UXlU5dhgai9yxyKRtTsJ40JNPlvaih6wlGeSExV0YXIYbukihkFyCeV+ZH3oppplkZpmWglu2OCWRNH54oJqF/dimZ1rGKGeHgKpXop1xoudnhXXmFih0YkXpIH1TqqiopNoluuSjlbI2KaWCYronhETVJyqhtWGVH0gkLkqYqTNytuerQsE46qlSYepoIqlnoikpnAumxyqWJSU66In9wUillH/mCuymK3YqpbDKNjsesp7pSW2wdFapYZ8wBgQAOw==" /></div>
<div align="justify">
Ни в одном из этих случаев эффективность поиска на бинарном дереве не будет высокой, потому что структурно дерево, содержащее эти данные, эквивалентно связному списку. Эффективность поиска <i>O(log N)</i> вырождается в <i>O(N)</i>. Учитывая то, что наибольший выигрыш от использования бинарных деревьев поиска достигается при работе с большими объёмами данных, результатом будет серьёзное падение производительности. К сожалению, классические методы поддержания дерева в оптимальном состоянии дико сложны. Простое решение - изучить усреднённый показатель эффективности на неком бинарном дереве поиска.</div>
<div align="justify">
В среднем эффективность базового бинарного дерева поиска составляет <i>O(log N)</i>. Здесь "в среднем" означает, что данные, поступающие на вход, достаточно случайны, чтобы создать то, что называется "рандомизированным бинарным деревом" (ну или случайным, уж простите за вольность), в котором каждый новый элемент данных с вероятностью 1/N будет помещён в корень произвольного поддерева:</div>
<div align="justify">
Наиболее очевидный способ создания рандомизированного бинарного дерева - случайная перетасовка входящих данных. Но в большинстве случаев мы не имеем сразу доступа ко всем данным, которые должны храниться в дереве в данный момент времени. На самом деле, чаще всего мы будем добавлять по одному элементу данных из потока. Это крайне усложняет задачу перемешивания данных ещё до их добавления в дерево.</div>
<div align="justify">
Случайное бинарное дерево может быть создано, если каждый элемент набора данных имеет одинаковые шансы стать вершиной поддерева. Мы можем прибегнуть к упомянутым в предыдущей статье функциям вставки в вершину. При каждой вставке мы можем случайным образом использовать вставку в вершину или обычную вставку. Таким образом мы получим случайное бинарное дерево. Алгоритм выглядит неплохо и на удивление прост для реализации. В реализации нашего алгоритма будем использовать структуры <i>struct node</i> и <i>struct tree</i>, описанные в предыдущей статье:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;">1 struct node {
2 struct node* link[2];
3 int data;
4 };
5
6 struct tree {
7 struct node* root;
8 size_t size;
9 };</span></pre>
<br />
<div align="justify">
Возьмём простую рекурсивную вставку на основе функций из предыдущей статьи "Абстрактные типы данных - Бинарные деревья поиска":</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 struct node* insert_r (struct node* root, struct node* item, int key)
2 {
3 if (root == NULL)
4 root = item;
5 else {
6 int dir = root->data < key;
7 root->link[dir] = insert_r (root->link[dir], item, key);
8 }
9
10 return root;
11 }
12
13 void insert (struct tree* tree, struct node* item, int key)
14 {
15 tree->root = insert_r (tree->root, item, key);
16 ++tree->size;
17 }</span></pre>
<br />
<div align="justify">
Предполагая сортированные данные на входе, используя этот алгоритм мы получим вырожденное дерево. Однако, два простых изменения помогут сократить вероятность вырождения дерева до пренебрежительно малого значения. Первая модификация: добавим аргумент sz в прототипе <i>insert_r()</i>. Начальное значение этого аргумента будет задаваться с помощью <i>tree->size</i> в <i>insert()</i>. При каждом рекурсивном вызове <i>insert_r()</i> будет передаваться половина данного значения, которое является грубой оценкой числа элементов в поддереве.</div>
<div align="justify">
Вторая модификация: вставка в вершину; текущее поддерево будет вершиной в которую производится вставка. Вставка в вершину будет сделана с вероятностью 1/sz. В общем и в целом, подразумевая, что у нас есть функция <i>root_insert()</i>, изменения, сделанные нами в изначальном коде <i>insert_r()</i> и <i>insert()</i> будут выглядеть следующим образом:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 struct node* insert_r (struct node* root, struct node* item, int key, int sz)
2 {
3 if (root == NULL)
4 root = item;
5 else if (rand () % (sz + 1) == sz) /* Вероятность 1/sz */
6 root = root_insert (root, item, key);
7 else {
8 int dir = root->data < key;
9 root->link [dir] = insert_r (root->link [dir], item, key, sz / 2);
10 }
11
12 return root;
13 }
14
15 void insert (struct tree* tree, struct node* item, int key)
16 {
17 tree->root = insert_r (tree->root, item, key, tree->size);
18 ++tree->size;
19 }</span></pre>
<br />
<div align="justify">
Здесь мы используем вероятно не лучший источник случайных чисел, однако, на данный момент так будет проще. По поводу достоинств разных источников энтропии и их использования см. Random Numbers tutorial (<a href="http://eternallyconfuzzled.com/">eternallyconfuzzled.com</a>). Пока же мы ограничимся функцией стандартной библиотеки.</div>
<div align="justify">
Предположения о существовании <i>root_insert()</i> было достаточно на этапе "проектирования", но в реальном коде нам будет полезна конкретная реализация. Не вдаваясь в детали, возьмём такую примерную реализацию:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 struct node* root_insert (struct node* root, struct node* item, int key)
2 {
3 if (root == NULL)
4 root = item;
5 else {
6 struct node* save;
7 int dir = root->data < key;
8
9 root->link [dir] = root_insert (root->link [dir], item, key);
10
11 /* Поворот относительно вершины */
12 save = root->link [dir];
13 root->link [dir] = save->link [!dir];
14 save->link [!dir] = root;
15 root = save;
16 }
17
18 return root;
19 }</span></pre>
<br />
<div align="justify">
В этих функциях нет ничего нового, так что у любого, кто читал "Абстрактные типы данных - Бинарные деревья поиска", проблем с ними возникнуть не должно.</div>
<div align="justify">
Альтернативой обычной вставке с поднятием нового элемета данных в выбранную с некоторой вероятностью вершину является вероятностное объединение поддеревьев. Этот метод используется в "официальных" реализациях случайных бинарных деревьев поиска, как описано у Martinez и Roura. Алгоритмы вставки на верхнем уровне не особо изменились и в целом уже знакомы нам:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 struct node* insert_r (struct node* root, struct node* item, int key)
2 {
3 int n = (root == NULL) ? 0 : root->size;
4
5 if (rand () % (n + 1) == n)
6 return root_insert (root, item);
7 else {
8 int dir = root->data < key;
9 root->link [dir] = insert_r (root->link [dir], item, key);
10 ++root->size;
11 }
12
13 return root;
14 }
15
16 void insert (struct tree* tree, struct node* item, int key)
17 {
18 tree->root = insert_r (tree->root, item, key);
19 }</span></pre>
<br />
<div align="justify">
Вместо поиска вершины <i>root</i> со значением <i>NULL</i> единственным фактором при решении о месте вставки становится оценка вероятности. Заметьте, что вершина - это терминальный узел, n будет равно 0 и оценка вероятности будет такой, как нам и надо. Таким образом симулируется обычная вставка листового узла. Условия для предельного случая вставки на терминальном узле гарантированно соблюдаются.</div>
<div align="justify">
Операция объединения будет выполняться функцией <i>root_insert()</i> с помощью <i>split()</i>. <i>root_insert()</i> не производит поиск; вместо этого она просто резервирует место для нового элемента и объединяет с деревом на выбранной вершине. Такая операция может быть непростой, ведь небходимо гарантировать что дерево останется правильно построенным бинарным деревом. Поэтому <i>split()</i> должна быть рекурсивной функцией, чтобы выполнять очистку на поддереве, присоединённом к данной вершине. Естественно функция <i>split()</i> не обязана быть рекурсивной, но в данном случае рекурсия - это то, что делает жизнь легче:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 void split (struct node* root, struct node* item, struct node** s, struct node** g)
2 {
3 if (root == NULL)
4 *s = *g = NULL;
5 else if (item->data < root->data) {
6 *g = root;
7 split (root->link [0], item, s, &((*g)->link [0]));
8 } else {
9 *s = root;
10 split (root->link [1], item, &((*s)->link [1]), g);
11 }
12 }
13
14 struct node* root_insert (struct node* root, struct node* item)
15 {
16 struct node *s, *g;
17
18 split (root, item, &s, &g);
19
20 root = item;
21 root->link [0] = s;
22 root->link [1] = g;
23 root->size = 1;
24
25 root->size += s == NULL ? 0 : s->size;
26 root->size += g == NULL ? 0 : g->size;
27
28 return root;
29 }</span></pre>
<br />
<div align="justify">
Такая реализация функции <i>root_insert()</i> крайне проста. Большую часть работы она передаёт <i>split()</i>. <i>root_insert()</i> просто берёт результат выполнения <i>split()</i> и присоединяет полученный элемент к вершине root, попутно убеждаясь, что размеры изменены корректно и соответствуют структурным изменениям в поддереве. <i>split()</i> несколько сложнее. В частности то, как она рекурсивно обеспечивает правильную структуру дерева. Рассмотрим следующий пример:</div>
<div align="center">
<img alt="bst2_3" src="data:image/gif;base64,R0lGODlhbgBnAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///ywAAAAAbgBnAAAI/wAfCBxIsKDBgwgTKlzIsKHDhxAdApgIIKLFixgzHqRYcSBHjSBDihQ4kWHJkShTLuwoUaXLlw9YPpQJsyZGmjNt6rSIE2LPnUAN/mwZtOjGkEON2kzqU6lSpjmdFh3K8WRCqFJRUi2YFGtWpAppMvX6NWNXrivLLg1b1aTammcJjn0LM67HtHRfbpUbNm/dq2gB+3VJtW3fwSrJ4kWcUjFCx4yJ3oysVyNkyg0vx8SsF7JmzmkVfwYtOCbUkqNJB5ZLkbXVzapBjm39OLbZkam/fqyt1fZukrRJEib9+nHF3Lwpi15rvHhQz8yBv/6tUzNywM6FXi99cfvRqH9vh/9vWll2eZ7DRXqHPbkx7sSW4S8+/N58776n74NV7xbr9Z9VZccXf6FlVp9QEf23WH4EIkjefqUxaN93D8Z3VYDzibcRhhlq6OBd3FlIIXgTrjbgiCJ+SGKKJ7ZoIosuGthgiTSCuGKN6OnHFYchQtidfD7OmKOOMAZ5o5A/EgZdZ0US2aF7HjK50nooHtnZdMFVt+RTWRq1nG29Ccgae2CmSN2ZZTZpmphURpZbm4MhB2de2825E3U2HqjambS1iROeZYnp0ZzHZQmolxU2dpqdPboFpIxTtaenZHeqmaCk1VlqJaVSagppknBNeimOTromKJmjykYofai+mGiUUCaFJ5ysqW54aquivmhXrSbOdR6tu27655NG6sqqsEPGCuyxnMbo6KMoBtusjYc6i2SL0k47q7V5Kqtdlyry2i2u45YKarK9EnvtueKa+qySxW7arrfzvorur/eyWy+08trbr1ZbrvsuUF8CnO+di473qVN83qqqSYz6BK6WWDqcpqoWXyxVQAA7" /></div>
<div align="justify">
Скажем, мы хотим добавить в дерево узел <i>c</i>. <i>split()</i> нужно зарезервировать место для <i>c</i>, не нарушая структуру инвариантность бинарного дерева. Это достигается разделением двух поддеревьев нового узла без потери инвариантности. Здесь приведён пример разбиения поддеревьев для добавления узла <i>с</i>. Важным для для понимания алгоритма является момент, что каждый рекурсивный вызов оперирует на дочерних узлах <i>s</i> или <i>g</i>. (! обозначает текущий узел, ? - неопределённый указатель, ~ - NULL-указатель):</div>
<div align="center">
<img alt="bst2_4" src="data:image/gif;base64,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" /></div>
<br />
<br />
<div align="center">
<img alt="bst2_5" src="data:image/gif;base64,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" /></div>
<br />
<br />
<div align="center">
<img alt="bst2_6" src="data:image/gif;base64,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" /></div>
<br />
<br />
<div align="center">
<img alt="bst2_7" src="data:image/gif;base64,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" /></div>
<br />
<br />
<div align="center">
<img alt="bst2_8" src="data:image/gif;base64,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" /></div>
<div align="justify">
После того, как <i>split()</i> разбила дерево, отдельные части выглядят приблизительно как на следующей диаграмме. Теперь всё, что осталось <i>root_insert()</i>, использовать <i>s</i> и <i>g</i> для реструктуризации дерева. Попробуйте пройтись по коду самостоятельно и проверьте правильность результата:</div>
<div align="center">
<img alt="bst2_9" src="data:image/gif;base64,R0lGODlhBgHHAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///ywAAAAABgHHAAAI/wAfCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgfANi4MaPHjwg7AgBJsqTJkxBHElSJsuVDlR1dypxJcyJLgTdr6tQ4MOfOn0Bd+gyqk+VQokiTWuSotKbRplCjUowp1STMo1WzasW5laTIrmCj5sQadmrZs0THol3ac63bohzJvl3IdK7du3jz6t3Lt6/fv4ADCx5MGC7VwogT46y7+LDguIr/Oj44WS/knowDX8Yrt2BntJU9fwadefPa0Sv3ouZpuXNoratTc5YYu+rq2kBxt7WrW7fS2r6dsp0bPHjQ3mCNs3Zb/G1z2B6VF8UoXXjF6iWrY5epHTR16CHjyv/dLjS8eLpnu9tOqJb96ZARyZ9UL1Zhe/fp4afMb//17vr4/adfWFgBVxZZT9m3HnvnoXcggw0qmJyEAlImFYKeOUhggC9tyGFD8g03IFcUTjiiQyGChOF+F3644oMnguhhjBpCVWCGH5poUGwpqliijC2al5mF3lEm3nj8EVkhjkF+V6STMyqJZFbK9WhVdEmax5CVItpEHJRRdtmkl8499+R1OsZ3l5npsQmbgbORyZyYVC4HJGdwfinnVka5NhKXM92GJ21halRaaXn96aedeqJYqGivATrfSoceJumV/i3G6Jh0wmhkpI0aaimiO5nGZEaXYnlRqqhCeCN3ld7/JN94Q/4kHaurgpndoin6ZJqpgX6Eq1m6+tghSn1uuR2wm+aaprNeEYpssznGFyuJ0UIKKLPkdUvspGratOikv9a61LXUQotZpjt22uqejh67q7LUjYspujVKm+23d8orLIv8bjmfvUqiuS+8P/pbrMAIM2xsvO2+q6q7p+qrLsAKZ0xxtfQt3HDF4V4ccr8Wb5ywaySbrDFmHmNccr4jGxwwkihPLLJsLaf8spE5Q7wnyp91vDGCzOK8M2019wyzvkDrfHTARkeMbcExy1ztx05bO7WUDqvsctQVv4q1qElf/XTXw02Z7pJjH6321iD7fOrbNLadMLFER1h31ZDC/x332oy++PWSdP8Ntdwyi+0322NflelRdAu+MuBUG2535WnTqPjNWEd+N9pggx46pTAtLbrBkGO++OVZe27258sVzfjqrMlO++uN0V66xEojPvrUkk/uq+m38336brYn6PXkxutuO+VXD4879FkT//PygzdfvO/c21m41JxX77KbNp+9vfiwk3387Npb/zVyV1odXu/Td89+9ugjzi742R2+d/7uy9/+Api+7AmqJQP8nersx7z18a99AEwfuxIYvgXWz4L4iyD1/tdAApaLgqgi2EvyxLsMcvCCDiQg6YalrRVW8IGrIuEIzQfBE7YJhHQR4b86WEDLiYuGkenPqP9wqLUc1ote8WNgEEdIxHMNcVn4GhgSl8iX54WwideJIhW32BoscvGLYAyjGMdIxjKa8YxoTKMa18jGNrrxjXCMoxznSMc62vGOeMyjHvfIxz768Y+ADKQgB0nIQhpSReY6pB0/yEJFqkaHjmTjASPZRhlS8ozwuyQmsadJxZCvk1+sEijFKLRRLrGUpowMrcqWyiC+6HutLIzkYBnLx/TQh7X0S/BUmEvL3FKBvezLLn8ZzES5iJjFjNOAaJlMYeaQlc0EDCqjqUv6UXNNxWnkNYXyJ7Z4cZsXkhW/vgnOpPjKkhssp1g2RzxyqjNQkBTNFN9ZJybKKpE2pCdcsoj/RXfq05rc/OdxtJlPgUaHoP2xlj8vacV0DvSZ91zoIBnJszNRap65nGCyyoRLYGpSUAiN4fk8Gkl0EieTpkTpmjgpyE/OSX4fZemCZOpHUfKmfI6c5qPIhs+R/lGnz7ofDA9ZIL1hkE8F9WlNj4lCAB11qIScJS9t5KqeKnWPUkVmU9iZQkDmzaoO/U1Tr6rHrI5VrEklax7NmtatpjWkXNzcMHd6JGZGlaltdWsJc1pVuyJ1r4oEalCBOFGA2salJaUpp2pYSMS+h3WGVCk2CUtUk66Uh3ydZBUNRS+4jnGjCfUsmCgq1MjuKIqihem6uMpXIbLWopD9KU5hG1s4/8puWaF6oW1R2z/X0oRbB6OjRsOqPy1eMaK7C6761qjZ3nLWiCHEqI9IW0bLtqq5se1VPKko2Ydhtq2zMmFiHKvY7mqwqx0lDHn9p0SonvesalWNYd9L0vTm1b3olcxsVcvY5yq2vvc1JmDZy1jBTjXAl70ldgzsPKv65lap5Q56oMlfwgpurvk9sIAVFLT9WvhHr12fFSOMQIF1eL49dN19FefXalKIwpRFn4qfClXpwfemL87wjU84Y/zuzbr67WuLHVgXv/bYvr8DcpCphuEMOs7I+Wryj1erYWXGLcT9VeGFtTo7GyPYOZrbsY/buzajjrluXj6zMwdc2wsyeG7K9yNxeXQrXjK/2YIj3uJ6Y1xQcu6Zvr6s8H/D+mcuq1maBB60kg196EZLZtGCZrR5+YxkW353KpCuaJ0vDWBZble5jO50tzLtSeMGFLpHRDWoxYyYhiLQ1DF84qkT+k5X10uiIIK1QdUL1p0EBAA7" /></div>
<div align="justify">
Все эти трюки служат одной цели - сохранить инвариантность дерева, т.е., гарантию того, что каждый элемент по-прежнему имеет больший ключ, чем вершина его левого поддерева и меньший, чем его вершина его правого поддерева. Без этого нам бы пришлось использовать вставку у листа и вращения, чтобы избежать сложных проблем, связанных с коррекцией объединения.</div>
<div align="justify">
Заметьте: такая реализация <i>insert()</i> допускает повторение ключей. Есть несколько способов избавиться от дубликатов, начиная с самых простых, но не очень эффективных, до довольно сложных, но очень быстрых. При объединении самое простое решение - вызов <i>find()</i> перед каждой операцией вставки:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;">1 int insert (struct tree* tree, struct node* item, int key)
2 {
3 if (find (tree, key))
4 return 0;
5
6 tree->root = insert_r (tree->root, item, key);
7
8 return 1;
9 }</span></pre>
<br />
<div align="justify">
Удаление из такого рандомизированного бинарного дерева - это "просто" операция, обратная разбиению, метко названная объединением. Код функции remove на данный момент будет выглядеть так:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 struct node* remove_r (struct node* root, struct node** old_item, int key)
2 {
3 struct node* save;
4
5 if (root != NULL) {
6 if (key == root->data) {
7 save = join (root->link [0], root->link [1]);
8 *old_item = root;
9 root = save;
10 } else {
11 int dir = root->data < key;
12 root->link [dir] = remove_r (root->link [dir], old_item, key);
13 --root->size;
14 }
15 }
16
17 return root;
18 }
19
20 int remove (struct tree* tree, struct node** old_item, int key)
21 {
22 if (!find (tree, key))
23 return 0;
24
25 tree->root = remove_r (tree->root, old_item, key);
26
27 return 1;
28 }</span></pre>
<br />
<div align="justify">
<i>join()</i> удаляет вершину <i>root</i> и заново соединяет структуры с помощью ссылок, сохраняя правильное построение рандомизированного бинарного дерева поиска, так что мы и дальше можем пользоваться оценкой вероятности. Код лаконичен, но возможно не совсем ясен:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 struct node* join (struct node* left, struct node* right)
2 {
3 int ln = left == NULL ? 0 : left->size;
4 int rn = right == NULL ? 0 : right->size;
5
6 if (ln + rn == 0)
7 return NULL;
8 else if (rand () % (ln + rn) < ln) {
9 left->link [1] = join (left->link [1], right);
10 left->size += rn;
11 return left;
12 } else {
13 right->link [0] = join (left, right->link [0]);
14 right->size += ln;
15 return right;
16 }
17 }</span></pre>
<br />
<div align="justify">
Чтобы на примере рассмотреть работу функции <i>join()</i>, возьмём такое же дерево, как для примера со <i>split()</i>. Однако на сей раз мы будем удалять узел <i>e</i> вместо добавления <i>c</i> к <i>e</i>:</div>
<div align="center">
<img alt="bst2_10" src="data:image/gif;base64,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" /></div>
<div align="justify">
Принимая в качестве аргументов ссылки узла <i>g</i> (<i>d</i> и <i>h</i>), <i>join()</i> сначала проверяет, был ли узел <i>g</i> терминальным. С этой целью функция вычисляет сумму размеров поддеревьев узла. Если <i>g</i> был терминальным узлом, мы может просто заменить его указателем на <i>NULL</i>. Однако, в нашем случае у <i>g</i> есть два дочерних узла, так что придётся проделать чуть больше работы. Используя вероятность так же, как при вставке (1/sz), <i>join()</i> рекурсивно вызывает сама себя в поисках подходящей замены, основываясь на значении вероятности. Допустим, выбрано правое направление, тогда наше дерево примет такой вид:</div>
<div align="center">
<img alt="bst2_11" src="data:image/gif;base64,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" /></div>
<div align="justify">
Если бы был выбран левый путь, окончательная структура дерева выглядела бы так:</div>
<div align="center">
<img alt="bst2_12" src="data:image/gif;base64,R0lGODlhVQCJAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///ywAAAAAVQCJAAAI/wAfCBxIsKDBgwgTKlzIsKHDhwUBSAQAsaLFiwcnUhyoEaPHjwglMhQJsiTGjQ5Rmlw50qJKljAjnoxJk+PHlzVN4oS4M6fHnjx96lwJVGhKokZvJtRIcmnSn04JFi36NGpImQqpVjU4FavVrQ2nMm0J9ujXB1S1lhXYVepCtWvRnm0bl2xGr3fr2uU6NqvevUr/vi0J92/hvIIBVzxseGbilIzlPj56OHJjtlpFWo6LsyPHiW4nn+XblKtoxCA3pwZ9laXqxawxl37d2mfpkBRpj3Yd1DZp1ZF1+/Usu7Ljmrczqn0t3PTDzIFhBvebWvpxvNGrQ3VOGOl27Kh3u//sDl4mdO2rQw8Pi7499OXtC/Zzsm3g/Pqo09ocnL3gPTm3DHSPEwHmx59k1F1n0H320WeVWH2JZxZCAxb4wH8WcoeghhtyOF5C/TDBlAr4GRgeWxJOeBCGAhL4VU87AUVbUQwK5I+DtXX43Y7+ASjgHBmqt19/ChJU4wNHlsecdyv6KFCFOQ55UXNURvmcdfFJNV1MSx7IHk1dvpVZcyeqOFJsaKGJ3JZTqpmUcaexlxxfcX5ZnJZz1unhZ3nqWaafPAIaqKC4zUnmWsSlSdKhYPX5GaEp7kkom5DqaCagYQqaqZ6OXnpalX5C2CmjWOZIV52nWooip16qumqc+gmyKalgsUo562VzJfhpq6mKdmqvvmaV6J+PgcrqUKHqRiqYRB5bZKjPJvthpbaKSW1EY7567Z18vrRso27Kum1v405bLrnnXpkuutyu61aiw5bbaZrpwrktpZBu9i2zg6LaLKzkQVtovI8KTOzBtEZa3mQyshvsUkzVuqvCCydMsbjF6mrnxFa2+rByESKMa5Yfk8wwk872y/GUlW6KabScBrdvTvaOO++8k6JJsLs7u+tRQAA7" /></div>
<div align="justify">
Так как трассировка выполнения этого алгоритма тривиальна, а трассировка рекурсивного алгоритма к тому же хорошая практика, я оставляю вам выяснение того, как алгоритм создаёт окончательную структуру представленных деревьев.</div>
<a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036" name="bst2_section0"></a><br />
<h4>
Дерамиды (декартовы деревья) <a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036#bst2_top">[наверх]</a></h4>
<div align="justify">
Вычурно названные дерамиды являются структурой данных, совмещающих в себе свойства бинарной кучи и бинарного дерева поиска (вообще, этот АТД наверно более известен под названием "декартово дерево"). Если генерируемый для кучи приоритет - случайное число, то результат такой гибрилизации - на удивление хорошо сбалансированное рандомизированное бинарное дерево поиска. Дерамида (далее всё-таки буду придерживаться более распространённого названия в русскоязычной среде - "декартово дерево") должна подчиняться двум жёстким правилам:</div>
<div align="justify">
Как бинарное дерево (для <i>u</i> с левым дочерним узлом <i>v</i> и правым - <i>w</i>):</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;">1 v->data < u->data < w->data</span></pre>
<br />
<div align="justify">
Как куча:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;">1 w->priority < v->priority < u->priority</span></pre>
<br />
<div align="justify">
При присвоении приоритетам случайных значений или хэшировании данных с одной стороны и соблюдении условий для бинарной кучи и дерева мы получаем прекрасную возможность построить хорошо сбалансированное дерево. В худшем случае его эффективность всё же будет линейно зависеть от числа элементов <i>O(n)</i>, как и в случае прочих разновидностей рандомизированного бинарного дерева. Однако вероятность вырождения дерева не так велика. Там, где гарантия лучшего случая не является необязательной, декартово дерево демонстрирует приемлемую эффективность и его легко реализовать.</div>
<a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036" name="bst2_section1"></a><br />
<h4>
Вставка в декартово дерево <a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036#bst2_top">[наверх]</a></h4>
<div align="justify">
Вставка в декартово дерево - простая операция. Имея на входе новый элемент данных и случайный приоритет, просто осуществляем вставку в бинарное дерево поиска, а затем идём по дереву обратно, вращая поддеревья, пока не будет удовлетворено условие бинарной кучи. Это один из относительно простых вариантов вставки элемента данных в дерево не у листа. Вращая поддеревья, мы можем гарантировать, что свойства бинарного дерева сохраняются. Однако, в оригинальную структуру <i>struct node</i> надо внести небольшие изменения:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 struct node {
2 struct node* link [2];
3 int priority;
4 int data;
5 };
6
7 struct node NullItem = { &NullItem, &NullItem, RAND_MAX, 0 };
8 struct node *Null = &NullItem;
9
10 struct tree {
11 struct node *root;
12 };</span></pre>
<br />
<div align="justify">
Мы добавили поле приоритета. Кроме того, я определил две глобальные переменные типа <i>struct node</i>, проинициализированные предельными значениями, и указателями на себя. Наше декартово дерево будет использовать <i>Null</i> вместо <i>NULL</i> для обозначения терминального листа. Это выглядит, как необязательная модификация, но это изменение сильно упрощает алгоритм удаления.</div>
<div align="justify">
Рекурсивный алгоритм вставки, реализованный в insert для декартова дерева - сама простота:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 struct node* insert_r (struct node* root, struct node* item, int key)
2 {
3 if (root == Null)
4 root = item;
5 else {
6 int dir = root->data < key;
7
8 root->link[dir] = insert_r (root->link [dir], item, key);
9
10 if (root->priority < root->link [dir]->priority) {
11 struct node* save = root->link [dir];
12 root->link [dir] = save->link [!dir];
13 save->link [!dir] = root;
14 root = save;
15 }
16 }
17
18 return root;
19 }
20
21 void insert (struct tree* tree, struct node* item, int key)
22 {
23 tree->root = insert_r (tree->root, item, key);
24 }</span></pre>
<br />
<div align="justify">
Эта функция невероятно проста. Возможно, единственный момент, который может показаться неясным, использование переменной <i>dir</i> для определения направления поворота. Однако, понимание этой идиомы позволит Вам использовать более короткий и простой код. Этой сложности можно избежать при явной обработке двух случаев отдельными ветками условия, если угодно:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 if (key < root->data) {
2 root->link [0] = insert_r (root->link [0], item, key);
3
4 if (root->priority < root->link [0]->priority) {
5 struct node* save = root->link [0];
6 root->link [0] = save->link [1];
7 save->link [1] = root;
8 root = save;
9 }
10 } else {
11 root->link [1] = insert_r (root->link [1], item, key);
12
13 if (root->priority < root->link [1]->priority) {
14 struct node* save = root->link [1];
15 root->link [1] = save->link [0];
16 save->link [0] = root;
17 root = save;
18 }
19 }</span></pre>
<br />
<div align="justify">
Конечно, это делает ненужным применение массива ссылок в <i>struct node</i>, но вполне соответствует традиционному дизайну бинарных деревьев поиска, принятому в книгах и обучении.</div>
<div align="justify">
Те, кто внимательно читал "Абстрактные типы данных - Бинарные деревья поиска", могут обнаружить, что вставка в декартово дерево очень похожа на вставку в базовое бинарное дерево поиска у вершины. Таким образом, мы можем без труда преобразовать алгоритм в не рекурсивный:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 int insert (struct tree* tree, struct node* item, int key)
2 {
3 struct node* walk;
4 struct node* up [50];
5 int dir;
6 int top = 0;
7
8 /* Дерево пустое */
9 if (tree->root == Null) {
10 tree->root = item;
11 return 1;
12 }
13
14 /* Поиск пустой ссылки */
15 walk = tree->root;
16
17 for ( ; ; ) {
18 if (walk->data == key)
19 return 0;
20
21 dir = walk->data < key;
22
23 if (walk->link[dir] == Null)
24 break;
25
26 up [top++] = walk;
27 walk = walk->link [dir];
28 }
29
30 /* Вставка нового элемента */
31 walk->link [dir] = item;
32
33 /* Проход в обратном направлении и вращение */
34 while (item != tree->root) {
35 if (walk->priority > item->priority)
36 break;
37
38 dir = item == walk->link [1];
39 walk->link [dir] = item->link [!dir];
40 item->link [!dir] = walk;
41
42 /* Применяем изменения к остальным частям дерева */
43 if (top > 0 && up [top - 1] != Null) {
44 dir = (walk == up [top - 1]->link [1]);
45 up [top - 1]->link [dir] = item;
46 }
47
48 /* Сброс вершины, если надо */
49 if (tree->root == walk)
50 tree->root = item;
51
52 /* Двигаемся вверх и начинаем новый проход */
53 walk = up [--top];
54 }
55
56 return 1;
57 }</span></pre>
<br />
<div align="justify">
Очевидно, что это просто копипаст из "Абстрактные типы данных - Бинарные деревья поиска" с небольшими изменениями для обеспечения соблюдения свойств бинарной кучи. Что может быть проще, чем позаимствовать готовый код и адаптировать его под свои нужды?</div>
<a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036" name="bst2_section2"></a><br />
<h4>
Удаление из декартова дерева <a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036#bst2_top">[наверх]</a></h4>
<div align="justify">
Удаление узла - не такая важная операция, как вставка и часто её обходят без упоминания. Хотя я согласен с таким подходом, но всё же приведу небольшой кусочек кода, если он может оказаться полезным. Это чувство у меня появилось во времена тщетных попыток вывести полезный код удаления из примеров, когда я сам изучал структуры данных. Алгоритм удаления из декартова дерева в точности противоположен вставке, удивительно, но факт. Нам нужно нати элемент данных и произвести поворот (нисходя далее по дереву от нашего элемента), пока не достигнут лист, а затем наш элемент просто удаляется. Операция обычно называется "проталкиванием вниз" (продавливанием?), что лишь в очередной раз подтверждает наличие отсутствие особой фантазии у учёных, работающих в области теории вычислительных машин и систем. Алгоритм краток:</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;"> 1 struct node* remove_r (struct node* root, struct node** old_item, int key)
2 {
3 if (root != Null) {
4 struct node* save;
5
6 if (key == root->data) {
7 int dir = root->link [0]->priority > root->link [1]->priority;
8
9 save = root->link [dir];
10 root->link [dir] = save->link [!dir];
11 save->link [!dir] = root;
12 root = save;
13
14 if (root != Null)
15 root = remove_r (root, old_item, key);
16 else {
17 *old_item = root->link [1];
18 root->link [1] = Null;
19 }
20 }
21 else {
22 int dir = root->data < key;
23 root->link [dir] = remove_r (root->link [dir], old_item, key);
24 }
25 }
26
27 return root;
28 }
29
30 void remove (struct tree* tree, struct node** old_item, int key)
31 {
32 tree->root = remove_r (tree->root, old_item, key);
33 }</span></pre>
<br />
<div align="justify">
Поиск прост: если корень <i>root</i> не является терминальным узлом, двигаемся влево или право, соответственно. Иначе, совершаем поворот в соответствующем направлении и рекурсивно идём дальше, пока root не станет терминальным узлом. Если <i>root</i> является терминальным узлом, то простая его замена на <i>Null</i> достаточна для удаления узла из дерева. После удаления алгоритм должен пройти в обратном направлении вверх и исправить испорченные ссылки.</div>
<div align="justify">
Изучив этот код, Вы увидите, почему я предпочитаю специальные метки вместо простого указателя на <i>NULL</i> чтобы отметить терминальный узел. Если бы я использовал NULL-указатели, следующий кусок кода наверняка спровоцировал бы ошибку, т.к. при разыменовании <i>root</i> содержит неверный указатель.</div>
<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;">1 if (root != Null)
2 root = remove_r (root, old_item, key);
3 else {
4 *old_item = root->link [1];
5 root->link [1] = Null;
6 }</span></pre>
<a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036" name="bst2_section3"></a><br />
<h4>
Заключение <a href="http://www.blogger.com/blogger.g?blogID=3876463594612412036#bst2_top">[наверх]</a></h4>
<div align="justify">
Бинарные деревья поиска хороши для начала, но при этом имеющие место худшие случаи для них действительно худшие и что немаловажно, они отнюдь не редки. Используя рандомизацию при добавлении данных в дерево мы можем избегать худших случаев большу часть времени. Хотя я затронул лишь три вариации рандомизированных деревьев, на самом деле их больше. Не должно пройти незамеченным и сходство с алгоритмом быстрой сортировки. Базовый алгоритм быстрой сортировки похож на алгоритмы на базовых бинарных деревьях поиска: быстрый, но с такими же худшими случаями, коотрые очень сильно бьют по эффективности. Использование рандомизации в алгоритмах быстрой сортировки, и алгоритмах на бинарных деревьях поиска помогает минимизировать вероятность возникновения ситуаций, развивающихся по наихудшему сценарию. Таким образом эти алгоритмы становятся более полезными для среднестатистического программиста, причём особых усилий такое усовершенствование не требует.</div>
<div align="justify">
Оригинал этой статьи вы можете найти здесь: <a href="http://eternallyconfuzzled.com/">eternallyconfuzzled.com</a>.</div>
<div align="right">
<img alt="mind_fuck" src="data:image/gif;base64,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" /></div>
red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-22462970134371576782012-03-07T13:00:00.002-08:002012-05-05T08:44:59.466-07:00Примитивы синхронизации ядра - RCU<a name="top"></a><table><tr><td><a href="#rcu_section0">1. Авторские права и уведомления</a></td></tr><tr><td><a href="#rcu_section1">2. Введение</a></td></tr><tr><td><a href="#rcu_section2">2.1. Что такое механизм чтения-копирования при обновлении (RCU)?</a></td></tr><tr><td><a href="#rcu_section3">2.2. Почему именно RCU?</a></td></tr><tr><td><a href="#rcu_section4">2.3. Дополнительные источники</a></td></tr><tr><td><a href="#rcu_section5">3. Описание примитивов RCU</a></td></tr><tr><td><a href="#rcu_section6">3.1. struct rcu_head</a></td></tr><tr><td><a href="#rcu_section7">3.2. call_rcu()</a></td></tr><tr><td><a href="#rcu_section8">3.3. synchronize_kernel</a></td></tr><tr><td><a href="#rcu_section9">3.4. Использование барьеров памяти</a></td></tr><tr><td><a href="#rcu_section10">4. Применения RCU</a></td></tr><tr><td><a href="#rcu_section11">4.1. RCU в хэш-таблице со счётчиком ссылок refcnt</a></td></tr><tr><td><a href="#rcu_section12">4.2. Использование RCU совместно с kmem_cache_free()</a></td></tr></table><br /><a name="rcu_section0"></a><h4>1. Авторские права и уведомления <a href="#top">[наверх]</a></h4><p align="justify">Copyright © 2001 IBM Corporation. Авторские права защищены.<br /><br />Данный документ может воспроизводиться и распространяться в любой форме без предварительного разрешения при условии, что все авторские права сохраняются и данное уведомление воспроизводится во всех копиях. Модифицированные версии данного документа могут распространяться безо всяких ограничений, пока явным образом указывается факт модификации, а авторские права не нарушаются и включены в документ.<br /><br />Данный документ предоставляется "AS IS," ("как есть") без каких-либо явных или подразумеваемых гарантий. Используя информацию из данного документа, вы действуете на свой страх и риск.<br /><br />Особые замечания:<br /><br />Linux является торговой маркой, принадлежащей Линусу Торвальдсу. Прочие названия компаний, продуктов или услуг могут быть товарными знаками или марками обслуживания других компаний.</p><a name="rcu_section1"></a><h4>2. Введение <a href="#top">[наверх]</a></h4><p align="justify">Данный документ описывает использование механизма чтения-копирования при обновлении (Read-Copy Update, RCU) в ядре Linux для реализации масштабируемых взаимных исключений. Здесь приводятся примеры и пояснения относительно интерфейса. Также поясняется, как экономить циклы используя RCU.</p><a name="rcu_section2"></a><h4>2.1. Что такое механизм чтения-копирования при обновлении (RCU)? <a href="#top">[наверх]</a></h4><p align="justify">Мультипроцессорные системы требуют обеспечения последовательного выполнения критических секций кода, работающего с разделяемыми структурами данных. В традиционных реализациях ядер Unix используются разные механизмы обеспечения взаимного исключения, включая спин-блокировки, семафоры, спин-блокировки чтения-записи и т.д.. Контроль выполнения кода, манипулирующего разделяемыми данными, необходим также на однопроцессорных системах, когда код критической секции может быть выполнен и процессом, и из контекста прерывания. Механизмы краткосрочного взаимного исключения вроде спин-блокировок просты в использовании, однако с появлением более быстрых процессоров и на фоне отставания роста скорости взаимодействия с оперативной памятью они уже не столь привлекательны, т.к. накладные расходы при захвате блокировки растут с каждым новым поколением аппаратной архитектуры. Чем больше этот разрыв, тем большее число циклов процессора тратится впустую при ожидании на спин-блокировке для осуществления достаточно медленного взаимодействия с памятью. Вот почему возникла необходимость поиска альтернатив обычным моделям взаимного исключения на основе спин-блокировок.<br /><br />Механизм чтения-копирования при обновлении (далее по тексту просто RCU для краткости) является одним из таких методов взаимного исключения, при котором читающие потоки (потоки, получающие доступ к данным, но не модифицирующие их) могут обращаться к разделяемым структурам без захвата традиционной блокировки. В то же самое время пишущие потоки (потоки, модифицирующие данные) используют особую схему обратного вызова для обновления данных. Все глобальные ссылки на модифицируемые данные обновляются и замещаются ссылками на изменённые данные. Механизм обратного вызова используется для освобождения старых копий данных после того, как все процессоры в системе теряют локальные ссылки на разделяемые данные при переходе в неактивное состояние (например, переключение контекста). Т.к. обеспечение доступа для записи более затратно по сравнению с обеспечением доступа по чтению, RCU наиболее подходит для осуществления сценариев, когда защищаемые данные чаще считываются, чем изменяются. На однопроцессорных системах RCU можно использовать, как альтернативу маскированию прерываний для обеспечения взаимного исключения. Т.о., RCU хорошо подходит для использования при обслуживании таблиц марштрутизации сетевых пакетов, таблиц состояний устройств, отложенного удаления структур данных, обеспечения многопутевого ввода-вывода (multi-path I/O) и т.д..<br /><br />Изначально механизм RCU был разработан для DYNIX/ptx, вариант UNIX от Sequent Computer Systems Inc., ныне часть IBM. Сходные методы были использованы в проектах Tornado и K42 в универститете Торонто и исследованиях IBM.<br /><br />RCU - метод взаимного исключения, использующий событийно-ориентированную природу операционных систем и позволяющий свести к нулю накладные расходы при доступе по чтению к данным, разделяемым несколькими процессорами. Для структур, которые предполагают в основном чтение, данный механизм снижает конкуренцию за захват блокировок между потоками и частые блокировки строк кэша, предоставляя неблокирующий доступ для чтения.<br /><br />RCU позволяет вам читать разделяемые данные так, как если бы в системе вообще не было иных процессором, осуществляющих доступ к данным в то же самое время. Когда вам нужно обновить данные, вы обновляете глобальный указатель на них и сохраняете старую копию данных до тех пор, пока все потоки ядра, выполняемые в данный момент, не закончат работу. Обновлённый указатель является гарантией, что ни у одного из процессоров больше нет ссылок на старые данные, которые, таким образом, можно смело удалить.</p><a name="rcu_section3"></a><h4>2.2. Почему именно RCU? <a href="#top">[наверх]</a></h4><p align="justify">Ниже приводятся доводы, почему вам следует использовать механизм RCU, а также объясняются мотивы начала разработок в этом направлении.<dl><dt>Растущая цена традиционных блокировок</dt><dd><p>Скорости процессоров увеличиваются в 1,5-2 раза каждый год. Однако, рост скорости соединений (цепи на печатных платах и шины) составляет где-то лишь на 15% в год. Т.о., рост издержек использования традиционных блокировок стимулирует введение таких схем взаимного исключения, которые бы не зависели так сильно от путей коммуникации между относительно быстрыми процессорами.</p><p>RCU позволяет избегать блокировок при доступе для чтения.</p></dd><dt>Устранение сложноразрешимых ситуаций гонок</dt><dd><p>Механизм RCU не несёт риска взаимных блокировок (deadlocks), в отличие от явных блокировок. Снижение такого рода риска позволяет сократить или вовсе избежать использования избыточного кода, который бы потребовался в противном случае для отслеживания и исправления, а также предотвращения взаимных блокировок. Это, в свою очередь, снижает стоимость разработки и обслуживания программного обеспечения.</p></dd><dt>Оптимальное использование кэширования при неблокирующем чтении</dt><dd><p>Устраняя необходимость взведения блокировок при операциях чтения RCU позволяет избежать забивания строк кэша, которые бы использовались для обеспечения блокировок. В случаях, когда данные чаще читаются, такая безблокировочная система заметно выгоднее. Одним из примеров может служить синтетический тест производительности - "chat", где множество клонированных задач разделяют между собой данные в files_struct. Даже при использовании блокировки чтение-запись в процессе чтения файла через fget(), слишком интенсивное использование строк кэша для обеспечения самих блокировок ощутимо снижает эффективность системы при большом числе процессоров. Используя RCU, fget() может обращаться к указателю на файл без захвата блокировок, что существенно улучшит производительность системы.</p></dd></dl></p><a name="rcu_section4"></a><h4>2.3. Дополнительные источники <a href="#top">[наверх]</a></h4><p align="justify">Дополнительную информацию об RCU вы можете найти на домашней странице RCU <a href="http://lse.sourceforge.net/locking/rcupdate.html">http://lse.sourceforge.net/locking/rcupdate.html</a>. Техническое описание реализации RCU в Linux было опубликовано на Linux-симпозиуме в Оттаве Ottawa в 2001. Этот документ можно найти здесь: <a href="http://lse.sourceforge.net/locking/rclock_OLS.2001.05.01c.sc.pdf">http://lse.sourceforge.net/locking/rclock_OLS.2001.05.01c.sc.pdf</a>.</p><a name="rcu_section5"></a><h4>3. Описание примитивов RCU <a href="#top">[наверх]</a></h4><p align="justify">RCU состоит из интерфейса, который регистрирует функцию обратного вызова. Эта функция вызывается, когда все процессоры в системе прошли через одно из "состояний покоя" (переключение контекста, холостой цикл или переключение на пользовательский код). Данная функция может быть использоваться для обновления разделяемых структур данных. Однако, единовременно не может быть произведено более одной операции обновления, поэтому сами обновления должны происходить последовательно и управляться каким-либо традиционным механизмом взаимоисключения. Типичный код функции обновления должен следовать нижеописанным шагам:</p><ol><LI>Захват блокировки.</LI><LI>Обновление глобального указателя на данные (struct rcu_head) либо новым значением - адресом блока новых данных, либо NULL. После этого шага любой новый поток ядра, выполняемый на другом процессоре, увидит только новый указатель.</LI><LI>Передача управления функции обратного вызова (call_rcu) для освобождения копии старых данных после прохождения всеми процессорами состояния покоя. Данный шаг гарантирует, что после удаления копии старых данных ни один из процессоров не будет ссылаться на эти данные.</LI><LI>Освобождение блокировки механизма обновления.</LI></ol><p>Необходимые определения находятся в заголовочном файле linux/rcupdate.h.</p><a name="rcu_section6"></a><h4>3.1. struct rcu_head <a href="#top">[наверх]</a></h4><p align="justify">Эта структура содержит всю необходимую для RCU информацию, которая используется для выполнения очередных обновлений. Каждая операция обновления описывается одной структурой типа struct rcu_head. Как правило, rcu_head внедряется в ту структуру, которая использует RCU для обновлений и поиска содержащихся в ней данных.</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
struct rcu_head {
struct list_head list;
void (*func)(void *obj);
void *arg;
};
#define RCU_HEAD_INIT(head) \
{ LIST_HEAD_INIT(head.list), NULL, NULL }
#define RCU_HEAD(head) \
struct rcu_head head = RCU_HEAD_INIT(head)
#define INIT_RCU_HEAD(ptr) do { \
INIT_LIST_HEAD(&(ptr)->list); (ptr)->func = NULL; \
(ptr)->arg = NULL; \
} while (0)</pre></span><a name="rcu_section7"></a><h4>3.2. call_rcu() <a href="#top">[наверх]</a></h4><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void call_rcu(struct, rcu_head, *head, void, (*func) (void, *head) );</pre></span><p align="justify">call_rcu вызывает callback-функцию func после того, как все процессоры прошли хотя бы через одно состояние покоя. В целях обеспечения лучшей производительности хорошая реализация RCU не дожидается завершения цикла покоя. Вместо ожидания код такой реализации помещает функцию в очередь и возвращает управение. Все функции обратного вызова будут выполнены пакетом когда все процессоры в системе пройдут хотя бы одно из состояний покоя. Функция обратного вызова может получить управление в контексте обработки нижней половины прерывания. Пользователь должен принимать это во внимание.</p><a name="rcu_section8"></a><h4>3.3. synchronize_kernel <a href="#top">[наверх]</a></h4><p align="justify"><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void synchronize_kernel(void);</pre></span>Данная функция интерфейса ждёт, когда все процессоры в системе пройдут через состояние покоя, вроде переключения контекста. Этот вызов блокирует выполнение вызывающего потока, поэтому он может быть использован только в контексте процесса. Вызов этой функции из контекста процесса полезен в ситуациях, когда производительность операции обновления данных RCU не критична. Например, выгрузка модуля не критична в плане производительности, поэтому использование этого интерфейса допустимо, если мы имеем дело с данными, защищёнными RCU.</p><a name="rcu_section9"></a><h4>3.4. Использование барьеров памяти <a href="#top">[наверх]</a></h4><p align="justify">Порядок выполнения операций чтения и записи неявно обеспечивается в критических секциях, защищаемых традиционными механизмами блокировки. Однако при безблокировочном чтении данных в случае RCU важно обеспечить явный порядок процессорных операций доступа к памяти. Ядро Linux предоставляет множество функций, реализующих барьеры памяти. Наиболее часто используемые при работе с RCU функции: wmb() и rmb().</p><p align="justify">Примечение: в будущих версиях ядра имена этих функций могут быть изменены на write_barrier() и read_barrier() соответственно.</p><p align="justify">Например, представим себе ситуацию, когда алгоритм не допускает существования неактуальной копии данных. Вы можете избежать этой ситуации, используя следующий подход:</p><p align="justify">На стороне обновляющего кода:</p><ul><LI>Пометить элемент данных, как устаревший</li><li>Удалить его из списка</li><li>Запланировать выполнение функции обратного вызова RCU, чтобы удалить данные после цикла покоя</li></ul><p align="justify">В читающем коде:</p><ul><LI>Для каждого элемента данных<br />Если элемент данных element устарел - продолжить</LI><li>если element->value == key<br />возвратить element;</li></ul><p align="justify">Если операции записи для маркировки элемента данных, как устаревшего, будут переупорядочены либо при компиляции, либо процессором в ходе выполнения кода, такой код может работать неправильно. Чтобы избежать переупорядочивания операций, вы можете использовать барьеры памяти, как показано ниже:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>Пометить элемент данных element, как устаревший
wmb(); /* write_barrier() later */
Удалить элемент данных из списка
Запланировать вызов callback-функции для удаления данных после цикла покоя</pre></span><p align="justify">Например, на процессорах типа DEC Alpha, wmb() не гарантирует, что записи будут видны читающему коду в том порядке, в каком они должны быть согласно логике пишущего кода, даже в том случае, если есть некая зависимость данных (установка указателя на элемент в начале цикла и использование этого указателя в теле цикла). На таких архитектурах порядок чтения должен быть обеспечен явным образом с помощью rmb():</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>Для каждого элемента
rmb();
если элемент element неактуален
продолжить
если element->value == key
возвратить element;</pre></span><p align="justify">На большинстве процессорных архитектур порядок обеспечивается по умолчанию, если есть зависимость данных. Чтобы избежать ненужных операций разрабатывается интерфейс read_barrier_depends(). Эта функция предусматривает rmb() только для архитектуры Alpha.</p><a name="rcu_section10"></a><h4>4. Применения RCU <a href="#top">[наверх]</a></h4><p align="justify">В этом разделе описывается несколько примеров применения механизма RCU.</p><a name="rcu_section11"></a><h4>4.1. RCU в хэш-таблице со счётчиком ссылок refcnt <a href="#top">[наверх]</a></h4><p align="justify">Одно из распространённых применений RCU - использование в списке, защищённом глобальным механизмом взаимного исключения, причём элементы этого списка также индивидуально защищены механизмом взаимоисключения. Обычно поиск в списке более частая операция, чем вставки и удаления.</p><p align="justify">Рассмотрим пример функций поиска и удаления для простого циркулярного двусвязного списка. При использовании традиционных блокировок код был бы громоздким в силу необходимости избегать взаимных блокировок и тому подобного. Код, приводимый ниже и использующий интерфейс call_rcu значительно короче и проще:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>struct element {
struct element *next;
struct element *prev;
atomic_t refcnt;
int address;
int data;
struct rcu_head rcu;
};
/*
* Ищет элемент с данным адресом, захватывает его блокировку и возвращает указатель на него.
* Возвращает NULL если такой адрес не был найден.
*/
struct element *search(int addr)
{
struct element *p;
struct element *p0;
p0 = &head[hashit(addr)];
rmb(); /* read_barrier_depends() */
p = p0->next;
while (p != p0) {
rmb(); /* read_barrier_depends() */
if (p->address == addr) {
atomic_inc(&p->refcnt);
return (p);
}
p = p->next;
}
return ((struct element *)NULL);
}
/*
* Вставка нового элемента в хэш-таблицу
*/
void insert(struct element *element)
{
struct element *p0;
p0 = &head [hashit(p->addr)];
p->next = p0->next;
/*
* Необходимо убедиться, что следующий указатель добавленных данных
* видим всем другим процессорам до собственно вставки. Это позволит
* обеспечить безблокировочный поиск узла данных во всём списке.
*/
wmb();
p0->next = p;
}
void myfree(void *obj)
{
struct element *p = obj;
/*
* Теперь мы можем безопасно проверить счётчик ссылок refcnt,
* т.к. никто больше не воспользуется неактуальным указателем
* на данный элемент после безблокировочного поиска через
* search().
*/
if (atomic_read(&p->refcnt)) {
/*
* Позволим другому механизму сборки мусора
* удалить его.
*/
return;
}
kfree(obj);
}
/*
* Удаляем указанный элемент из таблицы. Элемент должен быть под блокировкой.
*/
void delete(struct element *p)
{
/*
* Захватываем глобальную блокировку. Возможности взаимной блокировки нет,
* т.к. глобальная блокировка всегда захватывается после взведения блокировки
* отдельного элемента.
*/
spin_lock(&hash_lock);
/*
* Удаляем элемент данных из списка, освобождаем блокировку,
* удаляем элемент из памяти и возвращаем управление.
*/
p->next->prev = p->prev;
p->prev->next = p->next;
spin_unlock(&hash_lock);
call_rcu(&p->rcu, myfree, p);
return;
}</pre></span><a name="rcu_section12"></a><h4>4.2. Использование RCU совместно с kmem_cache_free() <a href="#top">[наверх]</a></h4><p align="justify">В отличие от [kv]free(), kmem_cache_free() требует дополнительного параметра в виде указателя на кэш, из которого был захвачен блок памяти. При использовании RCU это может быть проблемой, потому что мы имеем дело с обычным указателем (не структурой). Вы можете избежать этой проблемы, используя специальную функцию-деструктор для каждого кэша, который требует отложенного освобождения памяти. Если указатель на кэш - глобальная переменная, функция-деструктор может использовать эту переменную напрямую и вызывать kmem_cache_free() из функции обратного вызова. В ином случае внедрить указатель на кэш можно вместе с указателем на структуру типа struct rcu_head в данные, которые должны удаляться, так, что функция-деструктор сможет получить необходимую информацию оттуда.</p><p align="justify">Для случая с глобальным указателем на кэш наша функция будет выглядеть так:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>static kmem_cache_t xxx_cache;
struct xxx_entry {
struct xxx_entry *next;
struct xxx_entry *prev;
int flag;
int info;
struct rcu_head rch;
}
void xxx_destroy(void *ptr)
{
struct xxx_entry *xp = (struct xxx_entry *)ptr;
kmem_cache_free(&xxx_cache, (void *)xp);
}
void xxx_delete(struct xxx *xp)
{
call_rcu(&xp->rch, xxx_destroy, xp);
}</pre></span><p align="justify">Если указатель на кэш недоступен глобально, его можно внедрить в структуру данных и работать с ним при освобождении памяти, как показано ниже:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>struct xxx_entry {
struct xxx_entry *next;
struct xxx_entry *prev;
int flag;
int info;
kmem_cache_t *cachep;
struct rcu_head rch;
}
void xxx_destroy(void *ptr)
{
struct xxx_entry *xp = (struct xxx_entry *)ptr;
kmem_cache_free(&xp->cachep, (void *)xp);
}
void xxx_delete(struct xxx *xp)
{
call_rcu(&xp->rch, xxx_destroy, xp);
}</pre></span>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-80276363691720650442011-11-05T06:41:00.000-07:002011-11-05T06:41:14.533-07:00Linux - работа с динамической памятью в ядре<p align="justify">К вящему сожалению разработчиков ядра выделение памяти в режиме ядра происходит не так просто, как в пользовательском приложении. Такая ситуация сложилась среди прочего по следующим причинам:</p><ul><li>Доступное ядру пространство ограничено 1Гб виртуальной и физической памяти.</li><li>Память ядра не выгружается.</li><li>Часто ядро требует физически непрерывных регионов памяти.</li><li>Зачастую ядро должно выделять память, не засыпая.</li><li>Ошибки в коде ядра обходятся куда дороже, чем где бы то ни было.</li></ul><p align="justify">Тем не менее, выделение памяти в ядре не такая уж роскошь и понимание некоторых тонкостей механизма выделения и работы с памятью в режиме ядра может сделать работу с памятью легче.</p><h4>Общий интерфейс</h4><p align="justify">Общий интерфейс для работы с динамической памятью в ядре представлен функцией kmalloc():</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#include <linux/slab.h>
void * kmalloc(size_t size, int flags);</pre></span><p align="justify">Эта функция очень похожа на старую добрую malloc() из стандартной библиотеки С, используемой пользовательскими приложениями. Исключение составляет то, что kmalloc() принимает 2 аргумента. Второй аргумент - флаги. Пока забудем про флаги. Первый аргумент - размер блока памяти - одинаково измеряется в байтах, как для malloc(), так и для kmalloc(). В случае успеха kmalloc() возвратит указатель на блок памяти запрошенного размера. Выравнивание выделенного блока памяти удобно для доступа и хранения объектов любого типа. Как и malloc(), kmalloc() может завершиться неудачей и поэтому возвращаемое значение должно проверяться на NULL:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>struct falcon *p;
p = kmalloc(sizeof (struct falcon), GFP_KERNEL);
if (!p)
/* запрос завершился неудачей - обрабатываем ошибку выделения */</pre></span><h4>Флаги</h4><p align="justify">Флаги управляют процессом выделения памяти, определяют поведение функции kmalloc(). Флаги можно условно разбить на 3 группы: модифицирующие собственно поведение; определяющие зону, откуда будет выделена память; тип выделения. Флаги, модифицирующие поведение kmalloc(), говорят ядру, как оно должно себя вести при выделении блока памяти. Например, может ли ядро приостановить выполнение вызывающего потока при выделении памяти (т.е., может ли вызов kmalloc() блокировать выполнение), чтобы удовлетворить запрос. Флаги зоны говорят, откуда именно должна быть выделена память. Например, блок памяти должен быть доступен аппаратному обеспечению - периферийным устройствам - через механизм прямого доступа к памяти (DMA). Наконец, флаги типа определяют тип выделения памяти. Флаги могут комбинироваться. Вместо указания нескольких флагов можно передать в качестве второго аргумента готовую, предопределённую комбинацию для типичных случаев.</p><p align="justify">В таблице 1 приводятся модификаторы поведения kmalloc(), а в таблице 2 - модификаторы зоны выделения. Могут быть использованы несколько флагов; выделение памяти в ядре нетривиальная задача. Флаги позволяют управлять многими аспектами выделения памяти в ядре. Ваш код должен использовать флаги типов, а не отдельные модификаторы поведения и модификаторы зон. Два наиболее часто используемых флага - GFP_ATOMIC и GFP_KERNEL. Почти весь код должен использовать один из этих двух модификаторов.</p><div align="center"><h5>Таблица 1. Управление выделением памяти (поведение ядра при выделении блока)</h5><table width="70%" border="1px" cellspacing="0" cellpadding="0"><th>Флаг</th><th>Описание</th><tr><td>__GFP_WAIT</td><td>Если для удовлетворения запроса недостаточно страниц, ядро блокирует вызывающий поток до появления свободных страниц.</td></tr><tr><td>__GFP_HIGH</td><td>Разрешения запросов к зарезервированным пулам - высокоприоритетный запрос (не путать с __GFP_HIGHMEM).</td></tr><tr><td>__GFP_IO</td><td>Разрешение на ввод-вывод при дефиците свободных страниц.</td></tr><tr><td>__GFP_FS</td><td>Разрешение операций файловой системы (VFS). Данный флаг не должен использоваться в коде, как-либо связанном с VFS, т.к. его использование может привести к бесконечной рекурсии.</td></tr><tr><td>__GFP_COLD</td><td>Разрешение на использование "холодных" страниц.</td></tr><tr><td>__GFP_NOWARN</td><td>Не выводить предупреждения в случае неудачи.</td></tr><tr><td>__GFP_REPEAT</td><td>Повторять попытки удовлетворить запрос на выделение до успеха.</td></tr><tr><td>__GFP_NOFAIL</td><td>То же, что __GFP_REPEAT.</td></tr><tr><td>__GFP_NORETRY</td><td>Не повторять запросы в случае неудачи.</td></tr><tr><td>__GFP_COMP</td><td>Фрейм принадлежит расширенной странице памяти.</td></tr><tr><td>__GFP_ZERO</td><td>Страница будет обнулена при успешном выделении памяти.</td></tr><tr><td>__GFP_NOMEMALLOC</td><td>Не использовать резервные пулы.</td></tr><tr><td>__GFP_HARDWALL</td><td>Данный флаг имеет смысл только для SMP-систем с NUMA (не 80x86 - Alpha, MIPS) и предписывает выделение страниц только из узла, принадлежащего данному процессору, который в свою очередь присвоен данному потоку.</td></tr><tr><td>__GFP_THISNODE</td><td>Как и предыдущий флаг, этот имеет смысл только для NUMA-систем и предписывает использовать выделение только из текущего узла памяти, запрещая обращения к другим узлам, либо узел, из которого должна быть выделена память необходимо задать явным образом.</td></tr><tr><td>__GFP_RECLAIMABLE</td><td>Страница памяти может быть возвращена системе.</td></tr><tr><td>__GFP_MOVABLE</td><td>Страница может перемещаться механизмом миграции.</td></tr></table></div><div align="center"><h5>Таблица 2. Модификаторы зон выделения</h5><table width="70%" border="1px" cellspacing="0" cellpadding="0"><th>Флаг</th><th>Описание</th><tr><td>__GFP_DMA</td><td>Память будет выделена из зоны DMA - первые 16Мб нижней памяти, к которым могут получить прямой доступ старые ISA-устройства.</td></tr><tr><td>флаг не задан</td><td>Память может быть выделена из любой зоны.</td></tr></table></div><p align="justify">Флаг GFP_ATOMIC уведомляет аллокатор, что запрос не должен блокировать. Данный флаг должен быть использован там, где поток не должен спать - например, в обработчике прерывания, обработчике нижней половины или в контексте потока, который держит блокировку. В силу того, что ядро не может блокировать вызывающий поток, такой запрос имеет меньше шансов на успех, ведь ядро не может приостановив поток, освободить дополнительную память, если памяти недостаточно для выполнения запроса. Запросы с флагом GFP_ATOMIC имеют меньший шанс на успех, нежели иные. Тем не менее, если вызывающий поток не должен спать ни при каких обстоятельствах, GFP_ATOMIC - единственно доступный вам тип запроса. Использование флага GFP_ATOMIC очень простое:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>struct wolf *p;
p = kmalloc(sizeof (struct wolf), GFP_ATOMIC);
if (!p)
/* error */</pre></span><p align="justify">Флаг GFP_KERNEL определяет обычный запрос памяти. Используйте этот флаг в контексте потока, который не держит никаких блокировок. kmalloc(), вызванная с этим флагом, может блокировать выполнение вызывающего потока; поэтому вы должны использовать этот флаг только в тех случаях, когда это полностью безопасно. Ядро может использовать блокировку вызывающего потока для того, чтобы освободить память, если это необходимо для удовлетворения запроса. По этой причине запросы с флагом GFP_KERNEL статистически имеют больший шанс на успех. Так, например, если недостаточно памяти, ядро может приостановить наш потом, выгрузить неактивные страницы на диск, урезать кэши ядра, сбросить на диск содержимое буферов и т.д..</p><p align="justify">В некоторых случаях, как например при создании драйвера ISA-устройства, вам может понадобиться обеспечить выделение памяти, которая была бы пригодна для операций прямого доступа (DMA). Для ISA-устройств диапазон такой памяти лежит в пределах первых 16Мб физической памяти. Чтобы обеспечить выделени блока памяти, соответствующего требованиям DMA, используйте флаг GFP_DMA. В общем, этот флаг вы будете использовать скорее всего либо в сочатении с GFP_ATOMIC, либо с GFP_KERNEL; флаги могут объединяться с помощью логического "ИЛИ". Например, чтобы проинструктировать ядро, что вам необходим блок памяти из зоны DMA и ваш поток может уснуть, вы можете использовать следующий код:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>char *buf;
/* нам нужен блок памяти для работы через DMA,
* и вызывающий поток может при необходимости спать */
buf = kmalloc(BUF_LEN, GFP_DMA | GFP_KERNEL);
if (!buf)
/* error */</pre></span><p align="justify">В таблице 3 приводятся флаги типов, а в таблице 4 показывается, какие флаги входят в тот или иной тип. Все эти флаги определяются в заголовочном файле <linux/gfp.h>.</p><div align="center"><h5>Таблица 3. Типы</h5><table width="70%" border="1px" cellspacing="0" cellpadding="0"><th>Флаг</th><th>Описание</th><tr><td>GFP_ATOMIC</td><td>Запрос на выделение памяти имеет высокий приоритет и не должен блокировать. Этот флаг полезен при запросе блока памяти из обработчика прерывания, обработчика нижней половины или в любой другой ситуации, когда поток не должен блокироваться.</td></tr><tr><td>GFP_NOIO</td><td>Запрос может блокировать вызывающий поток, но при этом не инициируются операции блочного ввода-вывода. Этот флаг полезен при использовании в контексте работы с блочным вводом-выводом.</td></tr><tr><td>GFP_NOFS</td><td>Запрос может блокировать поток, блочный ввод-вывод разрешён, но операции файловой системы (VFS) запрещены. Этот флаг полезен при использовании в коде VFS, когда нежелательны параллельные операции на файловых системах.</td></tr>
<tr><td>GFP_KERNEL</td><td>Это обычный запрос, который может блокировать поток. Данный флаг используется при выделении памяти в контексте потока, который может заснуть.</td></tr><tr><td>GFP_TEMPORARY</td><td>Временный блок памяти.</td></tr><tr><td>GFP_USER</td><td>Обычный запрос на выделение памяти, который может блокировать. Данный флаг используется с целью выделения памяти для пользовательских процессов.</td></tr><tr><td>GFP_HIGHUSER</td><td>Модификация предыдущего флага, которая предписывает kmalloc() выделить память по просьбе пользовательского процесса. Разница в том, что при использовании этого флага блок будет выделен из верхней памяти.</td></tr><tr><td>GFP_DMA</td><td>Запрос на выделение памяти из зоны DMA - памяти, поддерживающей прямой доступ. Используется в драйверах устройств, поддерживающих DMA.</td></tr><tr><td>GFP_DMA32</td><td>То же, что выше, но с расширенным пространством памяти DMA (до 4Гб).</td></tr></table></div><div align="center"><h5>Таблица 4. Комибинированные флаги</h5><table width="70%" border="1px" cellspacing="0" cellpadding="0"><th>Флаг</th><th>Описание</th><tr><td>GFP_ATOMIC</td><td>(__GFP_HIGH)</td></tr><tr><td>GFP_NOIO</td><td>(__GFP_WAIT)</td></tr><tr><td>GFP_NOFS</td><td>(__GFP_WAIT | __GFP_IO)</td></tr><tr><td>GFP_KERNEL</td><td>(__GFP_WAIT | __GFP_IO | __GFP_FS)</td></tr><tr><td>GFP_TEMPORARY</td><td>(__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE)</td></tr><tr><td>GFP_USER</td><td>(__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL)</td></tr><tr><td>GFP_HIGHUSER</td><td>(__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM)</td></tr><tr><td>GFP_DMA</td><td>__GFP_DMA</td></tr><tr><td>GFP_DMA32</td><td>__GFP_DMA32</td></tr><tr><td>GFP_THISNODE</td><td>(__GFP_THISNODE | __GFP_NOWARN | __GFP_NORETRY)</td></tr></table></div><h4>Освобождение памяти</h4><p align="justify">Если блок памяти, который вы выделили с помощь kmalloc(), вам больше не нужен, его нужно возвратить ядру. Для этого предусмотрена функция kfree(), которая похожа на свой пользовательский аналог из стандартной библиотеки С - free(). Прототип kfree() выглядит так:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#include <linux/slab.h>
void kfree(const void *objp);</pre></span><p align="justify">Использование kfree() аналогично использованию её пользовательского аналога. Допустим, p указатель на блок памяти, ранее выделенный с помощью kmalloc(). Тогда следующий код приведёт к освобождению блока и его возврату ядру:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>kfree(p);</pre></span><p align="justify">Как и в случае с free() вызов kfree() на блоке, который ранее был уже освобождён или на адресе, не ссылающемся на начало блока, выделенного с помощью kmalloc() приведёт к ошибке и, вероятно, порче памяти. Следите за выделением и освобождением блоков памяти, чтобы гарантировать вызов kfree() на правильном блоке памяти. Вызов kfree() с NULL в качестве аргумента проверяется отдельно и поэтому такой случай безопасен.</p><p align="justify">Давайте рассмотрим полный цикл работы с памятью, включающий как выделени блока, так и его освобождени:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>struct sausage *s;
s = kmalloc(sizeof (struct sausage), GFP_KERNEL);
if (!s)
return -ENOMEM;
/* ... */
kfree(s);</pre></span><h4>Выделение виртуальной памяти</h4><p align="justify">kmalloc() возвращает физически непрерывный блок памяти, а значит, непрерывный и виртуально. В противоположность этому пользовательский эквивалент - malloc() - возвращает непрерывный блок виртуальной памяти, но вовсе не обязательно непрерывный физически. У физически непрерывных блоков памяти есть два основных преимущества: во-первых, многие устройства не умеют работать с виртуальной памятью и поэтому для работы с такими устройствами блок памяти должен быть физически непрерывным. Во-вторых, физически непрерывный блок памяти может использовать одно единственное страничное отображение, что минимизирует использование буфера ассоциативной трансляции (TLB) при обращении к таким регионам, потому что используется лишь одна запись TLB.</p><p align="justify">Однако, при выделении непрерывных блоков возникает одна проблема: часто найти такой непрерывный блок памяти, в особенности, если речь идёт о большом блоке, сложно. Выделение памяти, которая непрерывна по диапазону виртуальных адресов в таких случаях имеет большее приимущество. Если у вас нет необходимости в непрерывном блоке, используйте vmalloc():</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#include <linux/vmalloc.h>
void * vmalloc(unsigned long size);</pre></span><p align="justify">Память, выделенная с помощью vmalloc(), возвращается функцией vfree():</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#include <linux/vmalloc.h>
void vfree(void *addr);</pre></span><p align="justify">Так же, как и с kmalloc()/kfree(), использование vfree() аналогично пользовательской функции free():</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>struct black_bear *p;
p = vmalloc(sizeof (struct black_bear));
if (!p)
/* error */
/* ... */
vfree(p);</pre></span><p align="justify">vmalloc() может блокировать вызывающий поток.</p><p align="justify">Во многих местах в ядре возможно использование vmalloc(), т.к. лишь немногие случаи требуют физически непрерывных блоков. Если вам нужно выделить блок памяти, с которым не будет работать какое-либо устройство и этот блок будет использоваться исключительно программным кодом - например, как в случае с данными, присвоенными пользовательскому процессу, необходимости в физически непрерывном регионе памяти нет. Тем не менее, в ядре vmalloc() используется не так часто. Большая часть кода использует kmalloc(), даже если это не так необходимо. Частично такая ситуация сложилась исторически, но отчасти это делается в интересах обеспечения производительности, т.к. использование TLB сокращается значительно. Не смотря на это, если вам нужно выделить блок размером в десятки мегабайт в режиме ядра, vmalloc() будет лучшим выбором.</p><h4>Фиксированный стек небольшого размера</h4><p align="justify">В отличие от пользовательских процессов, код ядра не располагает расширяющимся стеком большого размера. Каждый поток имеет небольшой стек фиксированного размера. Точный размер стека зависит от архитектуры. В основном под него отводятся 2 страницы, т.о. на 32-битных машинах стек имеет размер 8Кб.</p><p align="justify">Т.к. размер стека ограничен, автоматические выделения больших блоков памяти в стеке крайне нежелательны. Вы не должны увидеть в коде ядра нечто подобное:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#define BUF_LEN 2048
void rabbit_function(void)
{
char buf [BUF_LEN];
/* ... */
}</pre></span><p align="justify">Вместо этого предпочтительно делать так:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#define BUF_LEN 2048
void rabbit_function (void)
{
char *buf;
buf = kmalloc(BUF_LEN, GFP_KERNEL);
if (!buf)
/* error! */
/* ... */
}</pre></span><p align="justify">И наоборот, вы редко увидите такое в пользовательском пространстве, потому что заниматься динамическим выделением памяти в условиях, когда необходимый объём памяти известен в момент написания кода и она может быть выделена автоматически, не очень разумно. Но в ядре вы должны использовать динамическое выделение памяти всегда, когда требуемый объём памяти более или менее велик. Это позволяет избежать ошибок переполнения стека, которые в состоянии принести массу неприятных сюрпризов.</p><h4>Кое-что ещё о kmalloc() & Co</h4><p align="justify">Здесь мы коснёмся не столько интерфейса для работы с динамически выделяемой памятью, сколько с позволения сказать самих потрохов этого интерфейса. Ни в коем случае не стоит рассматривать этот раздел, как исчерпывающий источник по двум причинам: во-первых, код ядра всё-таки динамичен - что-то меняется, что-то добавляется, что-то со временем удаляется за ненадобностью. Это закономерный процесс. И хотя интерфейс работы с памятью, как таковой, едва ли может быть подвержен радикальным изменениям, то, что он скрывает, всё же может и будет меняться. В этом плане самый лучший источник - это первоисточник, т.е. сам код ядра. Во-вторых, подсистема управления памятью очень сложна. Не стоит недооценивать её сложность. Такая простая функция, как kmalloc() скрывает сотни строк кода для работы с кэшами, выделением страниц и ещё всякого разного и это не говоря о более низкоуровневом коде. О подсистеме управления памяти можно писать целые главы книг, а может даже и посвятить отдельную книгу. Естественно, в таком свете информация, приводимая здесь, будет лишь самой общей.</p><p align="justify">На самом деле, в ядрах версии 2.6.30 kmalloc() определяется в include/linux/slab_def.h и там же находится тело этой функции. kmalloc() определена, как встраиваемая. Иначе говоря, тело kmalloc() всегда подставляется в вызывающий код - нечто похожее на разворачивание макроса. Несмотря на то, что на первый взгляд работа kmalloc() прозрачна, эта функция скрывает за собой массу кода по работе с кэшами, трюки по оптимизации скорости выделения памяти и, конечно, она далеко не так проста, как может показаться на первый взгляд. Посмотрим на её код:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
struct kmem_cache *cachep;
void *ret;
if (__builtin_constant_p(size)) {
int i = 0;
if (!size)
return ZERO_SIZE_PTR;
#define CACHE(x) \
if (size <= x) \
goto found; \
else \
i++;
#include <linux/kmalloc_sizes.h>
#undef CACHE
return NULL;
found:
#ifdef CONFIG_ZONE_DMA
if (flags & GFP_DMA)
cachep = malloc_sizes[i].cs_dmacachep;
else
#endif
cachep = malloc_sizes[i].cs_cachep;
ret = kmem_cache_alloc_notrace(cachep, flags);
trace_kmalloc(_THIS_IP_, ret,
size, slab_buffer_size(cachep), flags);
return ret;
}
return __kmalloc(size, flags);
}</pre></span><p align="justify">Если пояснить этот код буквально парой фраз, то сразу видно, что реальная работа в некоторых случаях производится не самой kmalloc(), а __kmalloc(). Не во всех, а только в некоторых? Но в каких именно? kmalloc() пытается определить, является ли параметр size константой, известной на момент комплиляции кода. Т.е., не вычисляется ли её значение в ходе выполнения кода. Если size константа, то мы можем ускорить выделение блока памяти заранее известного размера. Как? Да очень просто: память будет выделена из кэша для объектов соответствующего размера, но кэш для объектов подходящего размера не будет искаться в процессе исполнения кода. Он уже найден на момент компиляции кода. Здорово, правда? Если же size не является константой, а вычисляется по ходу дела, в игру вступает __kmalloc(). Что делает __kmalloc()? Самое смешное - ничего, кроме того, что вызывает __do_kmalloc(), передавая последней те аргументы, которые получила сама от kmalloc(). Вы поинтересуетесь, к чему все эти матрёшки? Отвечу, что в природе всё не просто так и хоть есть известная шутка о том, что ядро linux пишут под порывом вдохновения (а hurd - под удары шаманского бубна), но здесь есть нечто рациональное. __do_kmalloc() принимает 3 аргумента вместо 2. Третий аргумент при вызове из __kmalloc() - NULL. Это указатель на вызывающую функцию, который может быть использован при отладке для трассировки запросов на выделение памяти. Таким образом, __kmalloc() - это просто обёртка, которая в зависимости от конфигурации ядра (отладочная или "production") просто позволяет осуществлять трассировку. Что ещё делает __do_kmalloc()? Исходя из сказанного выше, методом от противного нетрудно сделать вывод - она ищет подходящий кэш для объекта, если размер этого объекта (блока памяти) неизвестен на этапе компиляции. Что касается аргументов, с которыми работают функции __kmalloc() и __do_kmalloc(), то они полностью соответствуют оным для kmalloc().</p><p align="justify">Помимо нашей старой знакомой kmalloc() ядро предоставляет аналогичную функцию для выделения памяти из нелокального, явно заданного узла памяти. В начале статьи мы вскользь упоминали о NUMA-системах, в которых доступ к памяти неоднороден по скорости. Иными словами, в NUMA-системах память делится на зоны - участки памяти, к которым процессор обращается за определённое время. Возможно, это несколько непривычно, но как было упомянуто, есть архитектуры, где доступ к разным участкам физической памяти может занимать разное время. Ядро пытается минимизировать накладные расходы при работе на такой архитектуре и поэтому память делится на так называемые узлы - зоны, в пределах которых данный процессор может одинаково быстро получить доступ к памяти. Мы не будем подробно останавливаться на рассмотрении и описании всех тонкостей и особенностей NUMA и лишь упомянем, что для работы на NUMA-системах предусмотрена функция kmalloc_node(). Она принимает 3 аргумента - первые два, как обычная kmаlloc() и третий - целочисленный - номер узла памяти, откуда должна быть выделена память. В ядре, собранном без поддержки NUMA, kmalloc_node() вызывает обычную __kmalloc(), иначе по аналогии с kmalloc() работа разделяется между аналогичными функциями __*kmalloc_node(). kmalloc_node() определена в include/linux/slab_def.h, как и kmalloc(). Обратите внимание, что для освобождения памяти из нелокальных узлов не предусмотрено симметричной функции kfree_node(), что представляется вполне логичным, если учесть, что аллокатор располагает необходимой информацией о блоке памяти на момент его освобождения и в явном указании узла нет абсолютно никакой необходимости и смысла.</p><p align="justify">Чтобы завершить наше повествование об интерфейсе работы с динамической памятью упомянем ещё несколько функций, которые являются прямыми аналогами пользовательских: kcalloc(), krealloc() и вспомогательных функциях kzfree(), ksize() и kzalloc().</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>static inline void *kcalloc(size_t n, size_t size, gfp_t flags)
{
if (size != 0 && n > ULONG_MAX / size)
return NULL;
return __kmalloc(n * size, flags | __GFP_ZERO);
}</pre></span><p align="justify">kcalloc() очевидный и прямой аналог пользовательской функции calloc() - выделяет память под n элементов, каждый из которых имеет размер size. Выделенный блок памяти обнуляется.</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void * __must_check krealloc(const void *, size_t, gfp_t);</pre></span><p align="justify">Как и пользовательский эквивалент - realloc(), krealloc() изменяет размер ранее выделенного блока памяти. Первый аргумент - указатель на начало ранее выделенного блока, размер которого нужно изменить. Далее - новый размер блока и флаги. Примечательно, что krealloc() в действительности не предпринимает никаких действий, если новый размер блока меньше или равен прежнему. Если новый размер блока превышает старый, krealloc() выделяет новый блок памяти затребованного размера, освобождает старый и возвращает указатель на новый блок. Вот как выглядит реализация krealloc() из mm/util.c:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>/**
* __krealloc - like krealloc() but don't free @p.
* @p: object to reallocate memory for.
* @new_size: how many bytes of memory are required.
* @flags: the type of memory to allocate.
*
* This function is like krealloc() except it never frees the originally
* allocated buffer. Use this if you don't want to free the buffer immediately
* like, for example, with RCU.
*/
void *__krealloc(const void *p, size_t new_size, gfp_t flags)
{
void *ret;
size_t ks = 0;
if (unlikely(!new_size))
return ZERO_SIZE_PTR;
if (p)
ks = ksize(p);
if (ks >= new_size)
return (void *)p;
ret = kmalloc_track_caller(new_size, flags);
if (ret && p)
memcpy(ret, p, ks);
return ret;
}
EXPORT_SYMBOL(__krealloc);
/**
* krealloc - reallocate memory. The contents will remain unchanged.
* @p: object to reallocate memory for.
* @new_size: how many bytes of memory are required.
* @flags: the type of memory to allocate.
*
* The contents of the object pointed to are preserved up to the
* lesser of the new and old sizes. If @p is %NULL, krealloc()
* behaves exactly like kmalloc(). If @size is 0 and @p is not a
* %NULL pointer, the object pointed to is freed.
*/
void *krealloc(const void *p, size_t new_size, gfp_t flags)
{
void *ret;
if (unlikely(!new_size)) {
kfree(p);
return ZERO_SIZE_PTR;
}
ret = __krealloc(p, new_size, flags);
if (ret && p != ret)
kfree(p);
return ret;
}
EXPORT_SYMBOL(krealloc);</pre></span><p align="justify">Здесь же, в mm/util.c вы можете найти реализацию ещё одной функции, не имеющей аналога в стандартной библиотеке С - kzfree().</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>/**
* kzfree - like kfree but zero memory
* @p: object to free memory of
*
* The memory of the object @p points to is zeroed before freed.
* If @p is %NULL, kzfree() does nothing.
*/
void kzfree(const void *p) {
...</pre></span><p align="justify">В принципе, нельзя сказать, что эта функция делает нечто из ряда вон выходящее. На самом деле, она освобождает блок памяти с помощью kfree(), но при этом предварительно обнуляет содержимое памяти. Зачем? Для освобождения блока памяти, содержащего чувствительные данные, например. Как и kfree() kzfree() ничего не делает, если в качестве аргумента передан указатель на NULL.</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>size_t ksize(const void *);</pre></span><p align="justify">Предназначение этой функции - возврат размера блока памяти, выделенного с помощью kmalloc(), указатель на который был передан в качестве аргумента. Казалось бы, эта функция не очень востребована, так как обычно код, работающий с блоками памяти, полученными от kmalloc() и так знает размер. Однако, как мы увидели на примере kzfree() и krealloc(), иногда полезно иметь возможность получить такую информацию вне кода, вызвавшего ранее kmalloc(). ksize() изначально не была экспортируемым символом. В ядрах 2.6.29-rc5 эта функция использовалась для определения размера структур crypto_tfm в криптографических модулях при затирании чувствительных данных перед возвратом памяти системе. Возможно, эта функция и не заслуживала бы особого внимания и не была бы нужна вообще, если бы не одно обстоятельство, связанное с работой kmalloc(). Дело в том, что kmalloc() выделяет память под объект из кэша. Кэш оперирует именно объектами, а не блоками памяти произвольного размера. Объект в контексте такого кэша это, в принципе, тот же блок памяти, но имеющий некий предопределённый размер. Объекты различаются именно по размеру. Возможно, вы сталкивались с таким понятием, как пул объектов (так называется помимо прочего кэш объектов в ядре Windows NT)? Вот именно из таких пулов-кэшей kmalloc() с помощью нижележащей подсистемы управления кэшем объектов (SLAB, SLOB, SLUB, SLQB) - аллокатора - выделяет память. К чему это всё? Ещё не улавливаете? Кэш объектов выделяет блоки дискретного размера. Например, 16, 32, 64, 128, 65535, ... байт. Именно так, и никак иначе. А это в свою очередь означает, что если вы запросите у ядра блок памяти размером в 257 байт, аллокатор выделит объект размером 512 байт. Удивлены? Именно так, при нижелещащем аллокаторе SLAB или SLUB kmalloc() округляет размер блока в большую сторону до ближайшего размера объекта, с которым работает аллокатор. Запрашивая 257 байт вы фактически получаете 255 лишних байт, которые не могут быть использованы при удовлетворении других запросов до тех пор, пока вы не освободите ваш блок в 257 байт. Это бесполезный расход памяти, который является побочным эффектом скорости работы кэша. Но код, который запросил блок, знает истинный размер данных, хранимых в выделенном блоке и может использовать остаток блока для иных нужд, не запрашивая дополнительно новых выделений памяти. Это обстоятельство интенсивно экплуатируется в сетевой подсистеме при обработке сетевых пакетов. В этих условиях ksize() помогает выяснить реальный размер блока памяти, размер той части объекта кэша, которая реально используется и размер области, которая не используется, но доступна в пределах выделенного объекта.</p><p align="justify">И, наконец, последняя рассматриваемая нами функция:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>/**
* kzalloc - allocate memory. The memory is set to zero.
* @size: how many bytes of memory are required.
* @flags: the type of memory to allocate (see kmalloc).
*/
static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}</pre></span><p align="justify">Она делает именно то, что вы видите - просто возвращает блок памяти с обнулённым содержимым. Практически, это могло бы быть эквивалентно вызову malloc() и memset() в пользовательском приложении.</p><p align="justify">krealloc() и kcalloc() не имеют парных им *_node() функций. Для kzalloc() такая парная функция предусмотрена - kzalloc_node(), третий аргумент которой - целочисленный идентификатор узла, откуда должна быть выделена память.</p><h4>Заключение</h4><p align="justify">Теперь, когда у вас есть некоторое представление о том, как ядро управляется с памятью, работа с этим ресурсом не должна показаться сложнее, чем в пользовательском процессе. Вот несколько простых правил, которых желательно придерживаться:</p><ul><li>Определитесь, может ли поток спать (может ли kmalloc() приостановить выполнение вашего потока). Если вы имеете дело с обработчиком прерывания, нижней половиной или если ваш поток держит блокировку, он не может спать. Если ваш код выполняется в контексте процесса и не держит блокировок, то вероятно, можно разрешить kmalloc() блокировать выполнение.</li><li>Поток может уснуть, если указан флаг GFP_KERNEL.</li><li>Если ваш поток не должен спать, указывайте флаг GFP_ATOMIC.</li><li>Если вам нужна память для работы с DMA (например, ISA-устройства или кривое PCI-устройство), используйте флаг GFP_DMA.</li><li>Всегда проверяйте возврат kmalloc() и обрабатывайте возврат NULL-указателя.</li><li>Не допускайте утечек памяти; убедитесь, что на каждый kmalloc() где-то есть kfree().</li><li>Убедитесь, что ваш код не создаёт ситуаций, когда kfree() может вызываться многократно на одном блоке, а также, что к освобождаемому блоку не будет обращений после вызова kfree().</li></ul><p align="justify">В этой статье затронута интересная тема - менеджеры кэшей объектов (slab). Но я думаю, что было бы не очень уместно пытаться втиснуть всё в одну статью. Я надеюсь вернуться к этой теме и рассмотреть её более детально в одной из будущих статей.</p><h4>Источники</h4><p align="justify">Чтобы узнать больше, вы можете посмотреть эти файлы из дерева исходного кода ядра.</p><ul><li>include/linux/gfp.h: флаги.</li><li>include/linux/slab.h: определение функции kmalloc(), и проч.</li><li>mm/page_alloc.c: функции выделения страничных фреймов.</li><li>mm/slab.c: реализация функции kmalloc() и проч.</li></ul>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-6714617151003274322011-10-22T18:36:00.000-07:002011-11-01T22:13:33.772-07:00Абстрактные типы данных - Бинарные деревья поиска<a name="top"></a><table><tr><td><a href="#bst_section0">Концепция</a></td></tr><tr><td><a href="#bst_section1">Поиск</a></td></tr><tr><td><a href="#bst_section2">Вставка</a></td></tr><tr><td><a href="#bst_section3">Удаление</a></td></tr><tr><td><a href="#bst_section4">Уничтожение дерева</a></td></tr><tr><td><a href="#bst_section5">Обход дерева</a></td></tr><tr><td><a href="#bst_section6">Родительские указатели</a></td></tr><tr><td><a href="#bst_section7">Правовинтовые деревья</a></td></tr><tr><td><a href="#bst_section8">Производительность</a></td></tr><tr><td><a href="#bst_section9">Заключение</a></td></tr></table><br /><p align="justify">Деревья являются одной из старейших и наиболее часто используемых структур данных в программировании. В самых общих чертах деревья - это просто неориентированные графы, называемые свободными деревьями. Однако наибольшее распространение получили бинарные деревья поиска. Бинарные деревья поиска упрочились в употреблении так сильно, что если кто-то говорит о деревьях, то подразумевается обычно бинарное дерево поиска, поэтому далее, когда я пишу "дерево", я подразумеваю "бинарное дерево поиска". Если я пишу "бинарное дерево поиска", я подразумеваю "бинарное дерево поиска", что может приводить к путанице. Эта статья описывает базовые концепции деревьев, иногда очень детально.</p><p align="justify">Я постараюсь приложить все усилия, чтобы уделить внимание важным и практическим аспектам (сопровождая пояснения реальным работающим кодом), не вдаваясь в теорию. У меня нет желания писать ещё одну сухую книгу о деревьях, и поэтому в центре моего внимания находится то, что вы сможете использовать в реальном мире, не имея докторской степени. Если вы ищете леммы и доказательства, то эта статья не для вас. Я недостаточно компетентен и незаинтересован заполнять статью страницу за страницей математической каббалистикой. Если же вы хотите узнать, что за звери эти бинарные деревья поиска или просто не до конца понимаете что-то, вы пришли по верному адресу. Если вы уже использовали деревья в реальном коде или просто в примерах, эта статья позволит вам углубить знания и, возможно, познакомит с вариациями в работе с деревьями.</p><p align="justify">Я программист, а не учёный. Я зарабатываю на жизнь написанием кода, а не теориями. Поэтому у меня есть строгое мнение о бесполезности теории. Уверен, знание теории придаёт вам учёный вид, но иногда теория недоступна среднестатистическому Ивану, не играя к тому же особой роли на практике. Я хочу, чтобы моя статья была полезна как можно большему числу людей, поэтому я пишу для таких людей, как я сам - для людей, которые не забивают голову бесполезной теорией, если только это действительно необходимо для принятия каких-то важных решений по коду. Пишу я тоже, как программист, с каламбурами, непривычными шутками и различными социальными отклонениями. Это повод добавить немного текста в статью и разбавить текст юмором :-)</p><p align="justify">Прежде, чем мы приступим, несколько замечаний по коду. Весь код написан на C, потому что используя C я могу описать суть пояснений в тексте без вовлечения фреймворков. C++ или Java тоже были бы неплохим выбором, потому что они хорошо разрекламированы и в наши дни они лучше, чем Jolt!-кола, но при использовании этих языков пришлось бы писать классы в хорошем стиле, который принят для этих языков, что могло бы добавить много ненужной возни. Так как я пишу о деревьях, а не о том, как написать хороший класс для работы с деревом, я предпочитаю C, чтобы иметь возможность избавиться от ненужных деталей и фреймворка. Кроме того, C - база для общераспространённых языков. По своему синтаксису Java и C++ восходят к C, поэтому компетентному программисту не составит труда "перевести" код.</p><p align="justify">Я не "отупляю" свой код, потому что уверен - бесполезные примеры, написанные так, чтобы их смог понять любой, но в ущерб функциональности глупы и не помогают в конечном счёте никому. Я ожидаю от вас хорошего знания C, но тем не менее по ходу дела я буду объяснять моменты, которые недостаточно ясны. Код написан, как если бы он предназначался для готовой к использованию полноценной библиотеки с мыслью о том, чтобы его можно было использовать на самом деле! Я видел массу руководств, в которых код был написан с явными синтактическими и логическими ошибками, упрощающими допущениями, непрактичными конструкциями и проч.. Всё это делало такой код бесполезным где-либо, помимо самого руководства, в котором он приведён. С этой статьёй иначе - вам не только разрешается использовать приведённый здесь код, но даже более того - я настоятельно советую его использовать!</p><p align="justify">Вся эта статья является общественным достоянием (public domain). Это значит, что вы можете цитировать текст, не беспокоясь о соблюдении копирайтов, копировать всё содержимое статьи и размещать его где-то ещё и даже копировать код в свою библиотеку, которую вы намереваетесь продавать - всё это без необходимости какого-либо разрешения со стороны автора! Естественно, первоисточник может обновляться и быть более актуальным, но я не накладываю никаких ограничений на использование материалов. Вы можете также взять мой код и выдать его за свой, но это подпортит вашу карму. Я верю, что вы достаточно честны, чтобы упомянуть меня там, где такие упоминания необходимы. Написание руководств - тяжёлая работа, также, как и написание кода</p><a name="bst_section0"></a><h4>Концепция <a href="#top">[наверх]</a></h4><p align="justify">Итак, мы теперь друзья, так? Вы - программисты или хотите стать программистом, так что, наверняка вы сталкивались с необходимостью хранения кучи данных. Это может быть гистограмма уникальных слов в файле, сортировка огромной кучи IP-адресов, с которых были установлены соединения на сокете или просто база данных адресов электронной почты. Вероятно, вы использовали связный список или (Ха!) массив. Ни то, ни другое не подходит для нужд, описанных выше. То, что нам нужно - это структура данных, которая хранит данные в отсортированном виде без необходимости совершать дополнительные телодвижения с простыми операциями вставки и удаления, а также эффективным поиском. Ни связный список, ни массивы не обладают такими свойствами, а потому они являются плохими решениями.</p><p align="justify">Мы знаем, что бинарный поиск эффективен, потому что он делит последовательности, в которых производится поиск, на две половины на каждой итерации. Единственный недостаток - данные должны быть отсортированы. Давайте попытаемся сделать нечто безумное и составить список из элементов так, как эти элементы перебирались бы при бинарном поиске каждого из них. Т.е., в начале этой структуры данных был бы элемент из середины. Если мы поворачиваем направо от этого элемента, мы видим средний элемент из левого подмножества. В общем, мы берём линейный список и превращаем его в явно выраженную структуру бинарного поиска:</p><p align="justify">Последовательность <span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>[0][1][2][3][4][5][6][7][8][9]</pre></span> приобретает следующий вид</p><div align="center"><img alt="bt0" src="data:image/gif;base64,R0lGODlh1wCWAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///ywAAAAA1wCWAAAI/wAfCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzHgTAsaPGjyBDihxJcqDHgidLqlzJsiVGjgxhupxJs+ZMABBx2tzJs2dFnTl9Ch1KlCDQiEeLKl26MilSplCjgnQqkarUq1gXWg2atavXjRq3fh3LVGxHmQnFkl071OxDtWzj2lQL16Tcu0TdcsXLd67Cs3Xr9h38Ua9AuIIJK7aYGPHixyQba4VMWeTWo3Qraw77N2XazaAZcw5NuurL0qj3/kzNOmZisK1jw35tVLZtk5hN076tmOproGh5k76M2Olu4WyDI/R8WHlt5JVpAw4MHfLxic6rx82u8rr2ot4vhv//3nO8ePPkb+ZNn5X7zuJn2VOcHhOqVeYP8Mvf6Fx/fqnBuYfbfssVZxR63R2mGoH/vaWgVwIux+CDOSHY0nUWaobhV+FlaJ1o7Z2WnnkeWiYieSRihWCJfa14lYvfmeXfc1HRNSOF1V1Go4Q1TmbXZ9pJ9heAPuIIG3RCDmlfkQ0CiaSDTCoFn2M5OkTdkkoaaRCLd11ZH5ZONnnkk1GGWVaWYm6JIpplSommYVUWGB+bS9kYIZd4wUikeOylqOKJMQK653z7dcghoQRuONZuOOFJWIRjHgplmkFqSSeEkCrnaJcHurbpSMD1x92nyW0pKnBrGQfYl2TKqR+pown/apufXWUI658Z3QpiYbfZGqKJsnmoq2mgxlbisAuGhKxQXC7LaknOvpfgmSzZeSNP9EVaLH9zKrvqrb6d2pa4O0aGErnnqQpup1o5m6lOsMJr4HzzVmupmRdWSBOk5VqZbGSM7kusSwH/2yawAzeFqL3YTfrUtAtDu6vEqz2bsLKyTpUxr3xe+jDCFZvbscgTa3txrCNjnCvJKaOE8sshq+yxy9tuTOm53aopc1o3NttuQz4fTLPJPBYNs8733myzgl5q/LPFP/qLb8tjwnk00kLHXKDUQ0ON9dI4Wr2y10SXrKZgvjlsNNhNCngs0Fxz/BnaZXc9tdZyKp003nX3/7s0vHFHnbXgbNvtd+FNyz0b3H3vTfjYgR/OduKKH5jz2pI/3vjJQ4t9deaaQ3634aGD7njD7WZWM9Wfly65nk6bHTvflaM+utq1c577x6tHvPngsuMOsu6zE2/6zK0jX7zwvTP/+9csRw6x882TfTvmFEvfVJIE/2Y74zfVu++7Ncnr2k/ij69uec2t7xdu7jOG37XdX/6+5fRnj3P+XGe75qAEmxDpwFM+AbqOWfcTYLSANzwGLZCBO3Pgi6RlwAcqr3ryseD1oleoCSZQggBUD3L8dzzLrOp5EWzN/AKkPlPlhoLBMhC45uWuWRkMg1vDVrCMt7sLcrA0itLfDf/DckINfpB3KRwi+uKHGlotj4dISR9onNhD6iERgi1KHhS1d0XrLQZ2ogueEjeYxc7cKYnwoxLtuDgY1e3NV2Fy4/d890UmyZF12Bsg+NZYxjhi0Yt6PCAZ91jHN/0RePkLIh77aLQ7ihF6kCTk/gDJKUMOko2B1FvWXnjJUvmxk5LMoyBFqUkUkgVOnhMjJ0mJPOlEJ3WOXGR+6KfIs/HLiDi0Ih23SEq0qPFDsuQjJlsJOlz+UJfCPGQkxRTLx9SygcO03ipNKZeCKayLxrPfMp05xipuE5vd7I0Ut3c+b4ZyiuiaSzrN6UMNnTAv7zwmJQ3IF2vSU0PhvGc9x6kOz+iss59AjCdAByqXgAAAOw==" /></div><p align="justify">Такова идея, скрытая за деревом. Такая структура называется деревом из-за ветвей, как у настоящего дерева. Возможно, эту структуру стоило бы назвать корнем бинарного поиска, т.к. это было бы проще увязать с иллюстрацией, походящей скорее на корневую систему, но к сожалению в информатике уже укоренилось название "дерево", так что переименование наверно было бы плохой мыслью.</p><p align="justify">Если вы начнёте поиск элемента с самого верхнего узла, называемого корнем (путаница!), вы можете продолжать поиск заданного элемента, сравнивая его с вершиной на предмет того, меньше ли искомое значение, чем то, что содержится в вершине или же больше. Скажем, нам нужно найти элемент со значением 3. 3 меньше 5, поэтому мы двигаемся влево и теперь у нас вершина со значением 2. 3 больше 2, поэтому мы смещаемся вправо и теперь у нас вершиной становится узел 4, мы смещаемся влево от вершины и вот наше 3. 3 равно 3, вот мы и нашли наш элемент. Следуя этому образцу, вы можете найти любой элемент в дереве.</p><p align="justify">Хорошо, а что с неудачным поиском? Что, если искомого элемента нет в дереве? Очень просто! Проделайте поиск, как описано выше и если вы выйдете за нижнюю часть дерева, то поиск можно считать безуспешным. Ну, может это не так и просто, ведь мы не знаем, когда мы выйдем за нижнюю часть дерева. На самом деле, в дереве есть специальные узлы, называемые листьями (листовые узлы). Они не содержат данных и если мы достигаем такого листа, значит наш поиск зашёл в тупик. Для обозначения листьев я буду использовать символ тильды - ~, а дерево будет выглядеть так:</p><div align="center"><img alt="bt1" src="data:image/gif;base64,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" /></div><p align="justify">Из диаграммы легко увидеть, что при достижении листа поиск заканчивается. На будущее давайте определимся с терминологией. Вершина дерева называется корнем. Однако, учитывая то, что деревья могут определяться рекурсивно, каждый узел дерева может быть назван корнем (корень поддерева). Если узел является корнем поддерева, у него есть родительский узел, который ссылается на него. Так узел 2 является родительским узлом (родителем) узла 1, а 5 - родительский узел узла 2. Если смотреть с другой стороны, 2 является дочерним узлом узла 5, а 1 - дочерний узел (потомок) узла 2. В отношении деревьев принята аналогия с семейным древом, поэтому не удивляйтесь, если при обсуждении деревьев натолкнётесь на упоминания о дедах, прадедах и прочем таком.</p><p align="justify">Узлы, имеющие 2 потомка, называются внутренними, а узлы с 1 или меньшим числом дочерних узлов называются внешними. Лист - это лист. Высота дерева (или поддерева - ведь деревья рекурсивны) - это число узлов от корня до листа, исключая сам корень. На рисунке выше высота восьмёрки - 2, потому что самый длинный путь к листу имеет 2 узла - 7 и 6. Иногда вам также может встречаться случай, когда корень дерева также включается в высоту и тогда в нашем случае высота была бы 3. Оба способа верны до тех пор, пока их применение правильно.</p><p align="justify">Хорошо, поиск в таком дереве эффективен. А что с простотой вставки и удаления узлов? На первый взгляд, это непросто. Вставка в дерево нового узла проста и эффективна, если посмотреть на это с точки зрения безуспешного поиска. Если вы ищете новый элемент, который, как вы знаете, отсутствует в дереве, вы можете заменить лист на новый узел с тем значением, которое вы безуспешно искали. С помощью сравнений "меньше" или "больше" вы можете обрабатывать и дубликаты данных. Оба способа работают и ваши дубликаты будут выстраиваться в линию вниз по пути.</p><p align="justify">Удаление сложнее (но не настолько, как многие думают), однако мы оставим это на потом и рассмотрим в деталях чуть позже. Что у нас с сортировкой данных? Так как каждый левый дочерний узел меньше или равен по значению своему родителю, данные хранятся в отсортированном виде. Иногда использовать такое положение вещей эффективно не так легко, потому что следующая по порядку запись данных может не быть в прилегающем узле. Допустим, узлы 2 и 3 разделены узлом 4, а 4 и 5 - узлом 2. В дальнейшем мы рассмотрим способы извлечения пользы из такого расположения узлов, а пока всё, что вам нужно запомнить - данные в дереве уже действительно отсортированы!</p><p align="justify">Итак, деревья удовлятворяют всем нашим требованиям. Они предоставляют эффективную операцию поиска, данные в деревьях по умолчанию отсортированы, вставка новых записей проста и, если сейчас вы поверите мне на слово, удаление ненамного сложнее. Мы уже знаем, чего хотим. Давайте воплотим это в коде. Первое, что нам понадобится, это структура узла. Также было бы удобно работать с деревом, как с цельной сущностью, поэтому мы создадим структуру, которая просто будет содержать указатель на корень:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 struct node {
2 int data;
3 struct node *link[2];
4 };
5
6 struct tree {
7 struct node *root;
8 };</pre></span><p align="justify">Итак, у нас структура, ссылающаяся на себя. Если вы знакомы с тем, как работают связные списки, для вас понимание этой структуры не будет проблемой. Структура может содержать указатель на саму себя, так что, чтобы связать два экземпляра структуры, полю, указывающему на другой экземпляр, присваивается значение адреса этого другого экземпляра. Единственным моментом, вводящим в заблуждение, может оказаться массив ссылок. Я мог бы использовать два поля-указателя, но как вы вскоре увидите сами, операции на бинарном дереве поиска симметричны. Используя массив ссылок и булев индекс при доступе к его элементам мы можем избежать ненужных повторений кода лишь слегка уложнив его. Как только вы пообвыкнетесь с этой идиомой, она, возможно, покажется вам удобнее, чем обычная идиома левых и правых указателей. Для индикации листьев мы просто используем NULL, т.к. это общепринятая практика и она легка в использовании.</p><p align="justify">Наконец, с этими познаниями мы можем приступить к сути этой статьи. Начнём с поиска, ведь вы уже знаете, как он работает.</p><a name="bst_section1"></a><h4>Поиск <a href="#top">[наверх]</a></h4><p align="justify">Мы уже знаем, что поиск начинается с корня и дальше двигается либо влево, либо вправо в зависимости от того, каков результат сравнения значения из корня с искомым (сейчас мы пока не будем трогать деревья, которые допускают дубликаты). Далее мы переходим к правому или левому дочернему узлу корня и делаем то же, что проделали с корнем. В принципе, мы работаем с каждым поддеревом так, как если бы оно было уникальным деревом и на каждой итерации это повторяется. Всё это можно проделать рекурсивно. Код прост и почти устрашающ:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int find_r (struct node *root, int data)
2 {
3 if (root == NULL)
4 return 0;
5 else if (root->data == data)
6 return 1;
7 else {
8 int dir = root->data < data;
9 return find_r (root->link [dir], data);
10 }
11 }
12
13 int find (struct tree *tree, int data)
14 {
15 return find_r (tree->root, data);
16 }</pre></span><p align="justify">Базовыми случаями для рекурсии являются удачный и безуспешный поиск. Если корень равен указателю на NULL, мы достигли листа и можем возвратить код ошибки. Если данные из корня содержат значение, совпадающее с искомым, мы можем возвратить код успешного завершения. Иначе мы сравниваем искомое значение со значением из корня и в зависимости от результата переходим к его левому, если искомое значение меньше, чем значение корня, или правому дочернему узлу, если искомое больше. Код возврата при рекурсии всё время "отодвигается", так что find() возвращает то, что получает. 0 означает, что данные не найдены, а 1 - что поиск завершён успехом. Функция find() определена для удобства. Теперь пользователь может написать find (tree, data) и работать с деревом, как с чёрным ящиком вместо явного вызова find_r (tree->root, data).</p><p align="justify">Хорошо, я не могу представить себе, что этот код застопорит вас, а если это всё же случилось, возможно, вы ещё не готовы к знакомству с бинарными деревьями поиска. Однако фокус с флагом dir иногда вводит в заблуждение, поэтому я кратко поясню, как это работает. Мы знаем, что link [0] - это левое поддерево, а link [1] - правое поддерево, для перехода влево нам нужно убедиться, что сравнение даёт 0 в результате, а для перехода вправо - 1. Проблема. Ведь наиболее явный способ сравнения data < root->data даёт нам 1 и, следовательно, неправильное направление для дальнейшего движения, потому что нам нужно будет перейти влево. Поэтому, чтобы наша идиома с массивом работала, мы переворачиваем условие так root->data < data. При этом, если data меньше root->data, результат сравнения - 0 и мы получаем нужное направление.</p><p align="justify">Хотя при работе с деревьями рекурсия упрощает некоторые вещи, многие предпочитают нерекурсивные решения по множеству веских причин. Поиск в дереве без рекурсии ещё проще, чем рекурсивный. Он предотвращает вероятность переполнения стека, выполняется быстрее и требует меньше памяти:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int find (struct tree *tree, int data)
2 {
3 struct node *it = tree->root;
4
5 while (it != NULL) {
6 if (it->data == data)
7 return 1;
8 else {
9 int dir = it->data < data;
10 it = it->link [dir];
11 }
12 }
13
14 return 0;
15 }</pre></span><p align="justify">Пара замечаний по поводу дубликатов. Описанные алгоритмы подолжают поиск до первого совпадения и не позволят вам находить и перечислять дубликаты. Работа с дубликатами несколько сложнее. Если вам необходимо вести подсчёт дубликатов, возможно имеет смысл возвращать указатель на данные, сохраняя также указатель на узел, содержащий совпадение с тем, чтобы после вы смогли возобновить поиск там, где остановились в прошлый раз. Таким образом вы сможете найти последующие совпадения после первого. Многие деревья не допускают дубликатов. В этой статье мы в основном также исходим из этого. Это легче и для меня, так как мне не нужно писать дополнительный код для обработки дубликатов :-)</p><a name="bst_section2"></a><h4>Вставка <a href="#top">[наверх]</a></h4><p align="justify">Многие вещи, которые вы хотели бы сделать с помощью деревьев, вовлекают операцию поиска. Поэтому деревья столь эффективны. В идеале, поиск будет делить диапазон поиска надвое на каждой последующей итерации. Было бы неплохо использовать это обстоятельство и при реализации других операций. Например, вставка нового узла - это вариация алгоритма поиска с безуспешным результатом. Вы ищете значение, которого нет в дереве, достигаете листа и заменяете первый лист на новый узел с нужным значением:</p><div align="center"><img alt="bt2" src="data:image/gif;base64,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" /></div><p align="justify">Код, выполняющий эти действия, представляет собой простую вариацию алгоритма поиска, однако рекурсивный код извлекает некоторую выгоду из рекурсии для упрощения внесения обновлений. Вот рекурсивный код (make_node() просто создаёт новый узел, присваивает ему данные и инициализирует ссылки NULL-значениями):</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 struct node *insert_r (struct node *root, int data)
2 {
3 if (root == NULL)
4 root = make_node (data);
5 else if (root->data == data)
6 return root;
7 else {
8 int dir = root->data < data;
9 root->link [dir] = insert_r (root->link [dir], data);
10 }
11
12 return root;
13 }
14
15 int insert (struct tree *tree, int data)
16 {
17 tree->root = insert_r (tree->root, data);
18 return 1;
19 }</pre></span><p align="justify">Единственное отличие между insert_r() и find_r() состоит в возвращаемых ими значениях в двух основных случаях. Однако на первый взгляд может быть не совсем ясно, как работает рекурсивное обновление. Когда наша рекурсия сворачивается и мы поднимаемся вверх по дереву, мы присваиваем указателю на следующий узел то значение, которое возвращает insert_r(). Так мы обеспечиваем, что изменения в нижней части дерева дойдут и до верхних уровней. Кстати, распространённая ошибка - не забывайте сбрасывать родительский узел изменённого узла, чтобы ваши изменения вступили в силу.</p><p align="justify">Мы добавляем новые узлы, замещая листья внизу дерева, так что наше дерево растёт вниз. Это опять вызывает путаницу и снова на ум приходит мысль о "деревьях", как о "корнях". Давайте добавим в новое дерево несколько узлов, чтобы посмотреть, как это всё работает:</p><div align="center"><img alt="bt3" src="data:image/gif;base64,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" /></div><p align="justify">Как и функция find(), insert() может быть реализована без рекурсии - так же, как рекурсивные и нерекурсивные функции поиска. Нерекурсивная вставка имеет некоторые преимущества по сравнению с рекурсивной. При работе на больших деревьях вам не нужно беспокоиться от достижении некого предела рекурсии. Обычный цикл быстрее, чем вызов функции и памяти для хранения локальных переменных также требуется меньше. Иногда рекурсивные алгоритмы являются лучшим выбором из-за их простоты, но на мой взгляд многие нерекурсивные алгоритмы на деревьях не намного сложнее рекурсивных. Вообще, затрата усилий на создание нерекурсивного алгортма оправдывает себя.</p><p align="justify">Единственная разница между нерекурсивным поиском и нерекурсивной вставкой в том, что алгоритм вставки должен быть осторожным, чтобы не попасть на лист. Ввиду того, что листовой узел - это NULL-указатель, мы не сможем определить, с какого узла мы пришли и не сможем добавить ссылку на новый узел. Это не даёт осуществить саму вставку, потому что мы не сможем внести наши изменения в дерево. Поэтому рабочий цикл на каждой итерации сначала проверяет следующую ссылку прежде чем двигаться дальше. И если следующий узел - листовой, цикл разрывается, а новый узел записывается в переменную, содержавшую ссылку на лист. Так как у нас нет ссылки, которая указывала бы на корень дерева, мы рассматриваем это, как особый случай. Есть разные способы обработки случая пустого дерева, но я думаю, самый ясный способ такой:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int insert (struct tree *tree, int data)
2 {
3 if (tree->root == NULL)
4 tree->root = make_node (data);
5 else {
6 struct node *it = tree->root;
7 int dir;
8
9 for ( ; ; ) {
10 dir = it->data < data;
11
12 if (it->data == data)
13 return 0;
14 else if (it->link [dir] == NULL)
15 break;
16
17 it = it->link [dir];
18 }
19
20 it->link [dir] = make_node (data);
21 }
22
23 return 1;
24 }</pre></span><p align="justify">Так как условие, при соблюдении которого цикл разрывается, находится между кодом, выбирающим направление и кодом передвижения в этом направлении, мы используем бесконечный цикл. Конечно, можно построить и "нормальный" цикл, но я не думаю, что такие методы здесь уместны, а кроме того, коль скоро я автор статьи, моё мнение и решает :-) Обратите внимание, что в любой из этих реализаций вы можете разрешить дубликаты данных, если уберёте проверку на равенство. Всё будет работать и дальше, а дубликаты будут складываться один под другим.</p><a name="bst_section3"></a><h4>Удаление <a href="#top">[наверх]</a></h4><p align="justify">Та простота, с которой мы вставляли новые узлы, могла вызвать у вас необоснованное чувство безопасности. Хотя, если всё, что вам нужно, так это добавлять узлы и искать, всё в порядке. Но многие из нас на определённом этапе приходят к необходимости удалять из дерева существующие узлы. Это не так просто, как вставка узлов, т.к. у нас нет выбора, откуда удалять узел. Если это внешний узел, то всё почти так же просто, как и со вставкой. Но если удаляемый узел - внутренний, всё немного сложнее.</p><p align="justify">Всё, что нужно сделать для удаления внешнего узла - заменить его на его нелистового потомка или на лист, если у данного узла нет иных потомков, кроме листовых узлов. Есть три простых случая удаления внешнего узла. Если у нашей жертвы есть левый дочерний узел, мы заменяем удаляемый узел, который является внешним, левым потомком, потому как его правый потомок с уверенностью окажется листом. Если у удаляемого узла есть правый дочерний узел, то случай симметричен узлу с левым потомком. Если у узла нет дочерних узлов - выберите любой, т.к. они оба будут листьями. И не забудьте обновить ссылку родительского узла, чтобы она указывала на новый дочерний узел. Это удалит узел из дерева:</p><div align="center"><img alt="bt4" src="data:image/gif;base64,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" /></div><p align="justify">Здесь главное проследить, чтобы ссылка родительского узла была обновлена и указывала на узел с или лист. Если это не будет сделано, то узел х не будет успешно удалён из дерева, а последующее освобождение памяти приведёт к неприятностям. К счастью, пока у нас есть доступ к родительскому узлу, все три случая суммируются в одном блоке кода. Вероятно, это самый ужасный случай, о каком я могу подумать применительно к деревьям, потому что здесь необходимо проверять, какой из дочерних узлов х не пустой (NULL) и каким узлом по отношению к р является х таким вот образом:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 p->link [p->link [1] == x] = x->link [x->link [0] == NULL];</pre></span><p align="justify">Здорово, правда? Это похоже на удаление записи из связного списка. Ма замещаем ссылку на следующий узел узла р аналогичной ссылкой узла х, таким образом удаляя х из цепи и после этого можем со спокойной душой освободить память, занимаемую х. Оставшаяся часть - сравнение x->link [0] и NULL даёт нам 1, если левая ссылка пустая (NULL) - второй случай и мы будем использовать правую ссылку для замены узла х. Если правая ссылка также пустая, у х не было потомков (третий случай). Если проверка даёт 0, левая ссылка не пуста (первый случай). Сравнение указателя на х и правой ссылки узла р выполняет ту же работу, но иным способом. Если х - узел, на который указывает правая ссылка узла р, мы получаем 1. В противном случае результатом сравнения будет 0 и т.к. х не является листом, мы можем быть уверены, что х - левый дочерний узел р. Все действия выполняются в одной строке (особый случай - удаление корня дерева, - будет рассмотрен немного позже). Это простой случай удаления. Когда мы удаляем узел с двумя потомками, дело обстоит чуть сложнее.</p><div align="center"><img alt="bt5" src="data:image/gif;base64,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" /></div><p align="justify">Давайте удалим из этого дерева узел 5, просто представив, что это не корень :-) Мы не можем просто так заменить его одним из дочерних узлов, потому как это вызовет определённые неудобства. Самое заметное - что делать с одним из дочерних узлов, который окажется сам по себе? Хорошо, ну а как на счёт присоединения левого поддерева узла 5 в качестве левой ссылки узла 7 и заменой 5 на 8?</p><div align="center"><img alt="bt6" src="data:image/gif;base64,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" /></div><p align="justify">Проделав такое, мы соблюдаем правило, что левые узлы содержат меньшие значения, а правые - большие, но такая хирургия несколько нетривиальна. Мы можем сделать лучше с меньшим усилием, не подвергая структуру дерева таким сильным изменениям. Как на счёт простой замены 5 на 4 или 7, вместо беготни по поддеревьям? Данные узлы являются порядковым предшественником и преемником, соответственно. Вся работа сводится к тому, чтобы найти один из этих узлов, потому что всё, что в большинстве деревьев нужно сделать после - скопировать данные одного узла в другой, затем удалить порядкового предшественника или преемника, которым мы заменили удаляемый узел. В этой статье мы будем в этих целях использовать преемника, но на самом деле, это не имеет особой разницы (<i>Здесь возможна терминологическая неточность в переводе. В этом месте оригинал гласит следующее: These are the inorder predecessor and successor, respectively. All of the work is in finding one of those nodes, because then all you need to do in most trees is copy the data of one to the other, then remove the predecessor or successor that you replaced it with. Речь идёт о числах. Т.е., число, по порядку предшествующее 5 - т.е., 4 и следующее за ним из множества тех, что есть в дереве - 7. Я сходу не нашёл удачного эквивалента, потому что спешил, но в целях ясности решил оставить это замечание</i> - перев.)</p><div align="center"><img alt="bt7" src="data:image/gif;base64,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" />
<img alt="bt8" src="data:image/gif;base64,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" /></div><p align="justify">Прелесть этого приёма (удаление копированием) в том, что мы можем взять сложный случай, когда у удаляемого узла два потомка и свести его к случаю удаления узла с одним потомком. Как это работает? А если у старшего узла два потомка?</p><p align="justify">Младший узел никогда не имеет правого потомка, а старший - левого. Поэтому мы можем быть уверены в том, у старшего узла по меньшей один потомок, а его левая ссылка указывает на лист. Следуя этому, мы можем легко найти старший узел для заданного узла, если у нас есть доступ к его правому поддереву. Простой переход на правую ветку и нисхождение по левой стороне поддерева приведёт нас к старшему узлу. Схожим образом мы можем найти и младший узел, перейдя на левую ветку узла и пройдя по левому поддереву с правой стороны.</p><p align="justify">Теперь мы можем найти старший узел, следуя вниз по поддереву. Но что, если мы хотим найти старший узел для 4? Это сложнее, но мы не столкнёмся с такой ситуацией в базовом алгоритме удаления, так что можем пока оставить это в стороне. У нас достаточно условий для удаления узла из дерева. Сначала мы находим узел, который нужно удалить - так же, как и в алгоритме вставки, но в случае с удалением мы сохраняем указатель на родительский узел при спуске вниз. Когда мы достигаем нужного узла (если мы достигли листа, то просто возвращаем ошибку), мы смотрим, сколько у него потомков и применяем к нему тот или иной способ удаления в зависимости от того, простой у нас или сложный случай. Вот наш код:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int remove (struct tree *tree, int data)
2 {
3 if (tree->root != NULL) {
4 struct node *p = NULL, *succ;
5 struct node *it = tree->root;
6 int dir;
7
8 for ( ; ; ) {
9 if (it == NULL)
10 return 0;
11 else if (it->data == data)
12 break;
13
14 dir = it->data < data;
15 p = it;
16 it = it->link [dir];
17 }
18
19 if (it->link [0] != NULL && it->link [1] != NULL) {
20 p = it;
21 succ = it->link [1];
22
23 while (succ->link [0] != NULL) {
24 p = succ;
25 succ = succ->link [0];
26 }
27
28 it->data = succ->data;
29 p->link [p->link [1] == succ] = succ->link [1];
30
31 free (succ);
32 }
33 else {
34 dir = it->link [0] == NULL;
35
36 if (p == NULL)
37 tree->root = it->link [dir];
38 else
39 p->link [p->link [1] == it] = it->link [dir];
40
41 free (it);
42 }
43 }
44
45 return 1;
46 }</pre></span><p align="justify">Поиск узла почти идентичен тому, что используется в нерекурсивном алгоритме вставки, за тем лишь исключением, что здесь мы не разбиваем код определения направления и, собственно, переходов. Мы также присваиваем р значение it, прежде чем it будет присвоено содержимое it->dir (<i>Вообще-то, должно быть it->link [dir]. Тут у автора статьи явная опечатка</i> - перев.). Этим шагом мы сохраняем указатель на родительский узел и если р равен NULL, поиск завершён, а мы должны обработать особый случай - удаление корня дерева.</p><p align="justify">Давайте посмотрим на обработку двух случаев после окончания поиска. В первом случае, если у узла два потомка, нам нужно найти старший узел для него. Как вы уже знаете, чтобы сделать это, мы просто сдвигаемся на правую ветку и идём по левой стороне, пока не находим лист. Во время этой операции мы сохраняем ссылку на родительский узел текущего узла поддерева, так что в конце нисхождения succ будет содержать старший узел, а р - указатель на его родителя. Мы копируем данные из старшего узла в узел, который собираемся удалить, но фактически удаляется сам старший узел. Обратите внимание, что в этом случае мы работаем всегда с succ->link [1], потому что мы знаем: левая ссылка указывает на лист. Обратная проверка p->link [1] == succ в принципе даст тот же результат - 0, если на succ - указывает левая ссылка узла р и 1 - если правая. Теперь освобождаем память удалённого узла и дело сделано.</p><p align="justify">Во втором случае у удаляемого узла только один потомок, узел - внешний. Это простой случай, ведь нам не надо искать старший узел. Мы просто вырезаем узел, берём ту сторону, которая не указывает на лист и присоединяем поддерево к узлу р, как замену узла, на который указывает it. Если р равно NULL, мы пытаемся удалить корень дерева. Здесь может возникнуть проблема, потому что изменения, которые мы вносим, должны быть отражены в tree->root или они не будут сохранены, поэтому мы рассматриваем такую ситуацию, как особый случай. Такого особого случая не возникает, если узел, который мы удаляем, имеет двух потомков, потому что даже если такой узел окажется корнем дерева, мы просто перезапишем его данные, не удаляя сам узел.</p>
<p align="justify">Такой метод относительно короткий и простой, хоть и несколько наивный. Мы решаем проблему в лоб, хотя могли бы сработать хитрее. Отметьте, как оба случая на самом деле завершают удаление внешнего узла, хотя это всё ещё разные случаи, потому что в одном из них нам необходимо найти старший узел. Давайте попробуем сделать иначе. Вместо останова при нахождении внешнего узла мы просто сохраним указатель на него и продолжим движение вниз к внешнему узлу. Когда мы достигаем "дна", мы копируем данные текущего узла в ранее сохранённый узел и удаляем текущий:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int remove (struct tree *tree, int data)
2 {
3 if (tree->root != NULL) {
4 struct node head = { 0 };
5 struct node *it = &head;
6 struct node *p, *f = NULL;
7 int dir = 1;
8
9 it->link [1] = tree->root;
10
11 while (it->link [dir] != NULL) {
12 p = it;
13 it = it->link [dir];
14 dir = it->data <= data;
15
16 if (it->data == data)
17 f = it;
18 }
19
20 if (f != NULL) {
21 f->data = it->data;
22 p->link [p->link [1] == it] = it->link [it->link [0] == NULL];
23 free (it);
24 }
25
26 tree->root = head.link [1];
27 }
28
29 return 1;
30 }</pre></span><p align="justify">Эта функция вводит два приёма с целью избежания особых случаев. Первое - корень-пустышка, так что у корня дерева всегда есть родительский узел. Второе - сохранение ссылки на найденный и подлежащий удалению узел с тем, чтобы мы смогли скопировать данные, когда доберёмся до внешнего узла. Таким образом мы избавились от особого случая с удалением корня и поиском старшего узла. Теперь код стал короче и красивее. Заметьте, мы проверяем является ли значение it->data меньше или оно равно data, потому что мы хотим продолжит нисхождение в том числе после нахождения совпадения и нам нужно перейти на правую сторону, чтобы найти старший узел для этой записи.</p><p align="justify">Мы можем удалить узел и рекурсивно, но алгоритм такого удаления не очень гладкий. Приведённая ниже функция использует тот же подход, что и умная функция выше. Найдя узел, который мы хотим удалить, мы ищем для него старший узел и копируем данные. Мы заменяем данные узла, который искали данными из старшего узла (проверяя на условие меньше или равно,- после копирования данных старшего узла будет совпадение). Рекурсия продолжает раскручиваться, пока мы не достигнем старшего узла и на этом этапе мы совершаем простую операцию удаления. А затем, когда рекурсия пошла вспять, мы фиксируем все наши изменения:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 struct node *remove_r (struct node *root, int data)
2 {
3 if (root != NULL) {
4 int dir;
5
6 if (root->data == data) {
7 if (root->link [0] != NULL && root->link [1] != NULL) {
8 struct node *succ = root->link [1];
9
10 while (succ->link [0] != NULL)
11 succ = succ->link [0];
12
13 data = succ->data;
14 root->data = data;
15 }
16 else {
17 struct node *save = root;
18
19 root = root->link [root->link [0] == NULL];
20 free (save);
21
22 return root;
23 }
24 }
25
26 dir = root->data <= data;
27 root->link [dir] = remove_r (root->link [dir], data);
28 }
29
30 return root;
31 }
32
33 int remove (struct tree *tree, int data)
34 {
35 tree->root = remove_r (tree->root, data);
36 return 1;
37 }</pre></span><a name="bst_section4"></a><h4>Уничтожение дерева <a href="#top">[наверх]</a></h4><p align="justify">В какой-то момент вас достанут деревья и вы захотите избавиться от них. Но вы не хотите утечек памяти, поэтому вам нужно удалить каждый узел и освободить занимаемую память. Конечно, вы можете достичь цели многократным вызовом remove(), но это не то. Лучшее решение - обойти каждый узел дерева и удалить их все одним залпом. Пока не касаясь деталей обхода скажем, что это может быть сделано обходом в обратном порядке, а рекурсивный алгоритм тривиален:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 void destroy_r (struct node *root)
2 {
3 if (root != NULL) {
4 destroy_r (root->link [0]);
5 destroy_r (root->link [1]);
6 free (root);
7 }
8 }
9
10 void destroy (struct tree *tree)
11 {
12 destroy_r (tree->root);
13 }</pre></span><p align="justify">Можно проделать то же и без рекурсии. Но вообще говоря, нерекурсивный обход дерева в обратном направлении не очень лёгкий. Однако, вам не обязательно обходить дерево в обратном направлении, чтобы удалить его. Вы можете изменить структуру дерева так, как было бы удобно для ваших нужд :-) Если у нас есть дерево, в котором каждый левый потомок - лист, такое дерево очень легко обойти и удалить каждый узел: просто сохраняем правую ссылку, удаляем узел, переходим по сохранённой ссылке:</p><div align="center"><img alt="bt9" src="data:image/gif;base64,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" /></div><p align="justify">Используемый трюк позволяет любое дерево привести к подобной структуре. Достичь такой структуру нам позволит операция под названием вращение. Вращение может быть вправо или влево. Левый поворот превращает правого потомка узла в его родителя, а сам узел становится левым дочерним узлов. Правый поворот симметрично обратен левому:</p><div align="center"><img alt="bt10" src="data:image/gif;base64,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" /></div><p align="justify">Заметьте, как узел 2 двигается из левого поддерева в правое. Узлы, изменившиеся при вращении - 1 и 3. Левая ссылка узла 3 становится правой ссылкой узла 1, а правая ссылка узла 1 указывает на узел 3. Левый поворот на втором дереве превратит его в исходное - первое дерево. Т.е., вращение симметрично. Таким образом, если мы удаляем узел без левой ссылки, то мы совершаем вращение направо; когда левая ссылка указывает на узел, мы можем гарантировать, что будет обойдён каждый узел и каждый узел будет удалён. Код, который всё это делает, на удивление короткий:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 void destroy (struct tree *tree)
2 {
3 struct node *it = tree->root;
4 struct node *save;
5
6 while (it != NULL) {
7 if (it->link [0] != NULL) {
8 /* Right rotation */
9 save = it->link [0];
10 it->link [0] = save->link [1];
11 save->link [1] = it;
12 }
13 else {
14 save = it->link [1];
15 free (it);
16 }
17
18 it = save;
19 }
20 }</pre></span><p align="justify">Ну что ж, если вы ещё не заметили, то мы закончили с основами и дальше нас ждёт средняя ступень. Я разделил статью таким образом, чтобы лёгкий материал находился в начале, потому что мне сказали, что моё предыдущее руководство по бинарным деревьям поиска было сложным. С этого момента подразумевается, что вы хорошо усвоили основу и знаете, что делаете. В следующем разделе мы рассмотрим обход дерева. Эта тема может показаться замороченной, но я постараюсь изложить её доступно. После обхода дерева мы обсудим родительские указатели и резьбу, которые уже можно считать продвинутой ступенью. И, наконец, некоторые замечания по поводу призводительности и намёки, что ещё может последовать за основами.</p><a name="bst_section5"></a><h4>Обход дерева <a href="#top">[наверх]</a></h4><p align="justify">Теперь, когда у нас есть код для построения бинарных деревьев, мы можем вытворять с ними разные вещи. Полезные. Весёлые вещи. Естественно, вы можете использовать уже ранее описанную функцию поиска для того, чтобы определить, есть ли в дереве некое значение, но было бы неплохо проверять функцию вставки на предмет корректности её работы. Было бы также здорово иметь возможность выполнить некую операцию на всех узлах дерева, например, выводить на экран их содержимое. И это приводит нас к обходу дерева.</p><p align="justify">Наверно, вы подумали, что обход каждого узла в дереве прост и состоит из одного-двух случаев, которые необходимо обрабатывать. Верно? Нет! На самом деле, есть n! (факториал от n) разных путей обойти дерево, состоящее из n узлов, но большая часть таких маршрутов бесполезна. Из всего многообразия разных способов обхода бинарного дерева мы рассмотрим только две категории: обход в ширину - на примере обхода по уровням и обход в глубину - на примере обхода в прямом, симметричном и обратном порядке. Мы также рассмотрим более гибкий пошаговый обход, который вы можете найти в любой хорошей библиотеке для работы с деревьями.</p><p align="justify">Обход в глубину начинается с движения вправо или влево, насколько это возможно и продвигается до восхождения. Далее, переходя по ссылке вверх, обход снова движется направо или налево. Процесс провторяется, пока все узлы не будут посещены. Как и ожидалось, обход в глубину может быть написан рекурсивно, потому что маршрут движения построен на принципе стека. Конечно, возникает вопрос, а когда узел может считаться посещённым? Так как мы можем двигаться лишь в одном из двух направлений, нетрудно выяснить, что существует всего 6 путей обхода и посещения узлов при обходе в глубину. У нас есть три операции: "перейти влево", "перейти вправо", "посетить":</p><ol><li>посетить, перейти влево, перейти вправо</li><li>посетить, перейти вправо, перейти влево</li><li>перейти влево, посетить, перейти вправо</li><li>перейти вправо, посетить, перейти влево</li><li>перейти влево, перейти вправо, посетить</li><li>перейти вправо, перейти влево, посетить</li></ol><p align="justify">Из этих 6 вариаций 3 используются наиболее часто и имеют свои собственные стандартные названия: 1 - обход в пямом порядке, т.к. сперва посещается узел; 3 - симметричный обход, т.к. узлы обходятся в порядке сортировки их значений; и, наконец, 5 - обход в обратном порядке, потому что узел посещается после того, как совершены оба движения. Каждый из этих методов может быть реализован ввиде короткого и красивого рекурсивного алгоритма:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 void preorder_r (struct node *root)
2 {
3 if (root != NULL) {
4 printf ("%d\n", root->data);
5 preorder_r (root->link [0]);
6 preorder_r (root->link [1]);
7 }
8 }
9
10 void preorder (struct tree *tree)
11 {
12 preorder_r (tree->root);
13 }
14
15 void inorder_r (struct node *root)
16 {
17 if (root != NULL) {
18 inorder_r (root->link [0]);
19 printf ("%d\n", root->data);
20 inorder_r (root->link [1]);
21 }
22 }
23
24 void inorder (struct tree *tree)
25 {
26 inorder_r (tree->root);
27 }
28
29 void postorder_r (struct node *root)
30 {
31 if (root != NULL) {
32 postorder_r (root->link [0]);
33 postorder_r (root->link [1]);
34 printf ("%d\n", root->data);
35 }
36 }
37
38 void postorder (struct tree *tree)
39 {
40 postorder_r (tree->root);
41 }</pre></span><p align="justify">Давайте рассмотрим пример. Проследим за результатами каждого из способов обхода на дереве, что представлено ниже. Алгоритм обхода в прямом порядке сначала посещает узел, а потом продвигается. Узлы будут обойдены в таком порядке: 5, 3, 2, 7, 6, 8. Алгоритм симметричного обхода сдвигается в самую левую позицию, прежде чем посетить узел и даст такую последовательность: 2, 3, 5, 6, 7, 8. Обход в обратном порядке приведёт нас к последовательности 2, 3, 6, 8, 7, 5.</p><div align="center"><img alt="bt11" src="data:image/gif;base64,R0lGODlhhgByAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///ywAAAAAhgByAAAI/wAfCBxIsKDBgwgTKkQIoKHDhRAjSpxIsaLFhA8LZrzIsaPHjxEbQhQJsqTJkxIBUFSJsqXLkyxXvpxJs2JMmzVz6jR402LPnUBb/vQZtKhQkEONKsVZMunSpwuTOiQZFarViVKZXt3KEKNWrmAHZiUatqzUqVjLmu0qNqXasE4JxpX7FuxcgXcf5K0LdOjNvHv56jxLtargrYENH76amO1ixkgfc22sUbJdjpQt02xcWLNVlYkze37JsvNBkaJHw9RoemNq1U0ZTvULe+nr17Uv4tabO+ju3b1DmgQeXCFx4sVPo0SevO3y5lhnO14NHWPrwsyd85Re2/R2vDP/Xv/PPngleefeWVumfF5s6Mei2/P++vY2acx8fx/tKN+jfur+qeXXRiMByJ+APNElXGwMXmacWw3KxZ1iiBW4YGQPXlihbO99dNde/dGnHIQBZkgiZBSmqJuKJqLYIosiKpjWZCOCp+GBXsnkIGvp1Viijze66OFw0wX5GZERyngijUMiiSFcTya5Yn0/OokjlVPuh59g7G225WEdehljXT26t1OY8Ok1XlFl4hWilmqipRRo47353FoSyjnfegguyWWffpKJ5YxgAkpofoPqiCieZAlq6JhC7pglo1BWySSlX0qqaaaRXtrkho9a+pSdkE46qqMG2gbVbDGRRxiB/En/RyqP27WZKaxx6rambx+66uZIgdlqq5QvNoVmjoHeqaiVyWp3KJzLfgppfPddKWq0NnIapbalLvkfs6Y2au2e414b7rNXHgcut81q9Wq72Lo3bGrvXkibs8iaS+6H7P61IGBGiltZt0U6CyK5+RYrsJLwFpwtwl4BHHC84OHqsIWnWdwVvw1j/N3DCSfrr3BzHXvuwxwvjPK/CofcL8NAxjuyYmOh+zK+Mds8s4kD6tmyym7OW27QB4M8NLsU20zft8QSDGO0TDfdsccq2+cStetOjHW1SSPtMsxUX9310RcPPLWHvW6Wdkpr97VrTrhqHNXbc07Ipt2x+lydTgEBADs=" /></div><p align="justify">Итак, мы можем вывести на экран все узлы дерева. Но давайте сделаем что-нибудь весёлое, просто, чтобы разбавить монотонное повествование. Вместо получения значений узлов давайте посмотрим на структуру дерева. Это можно легко сделать с помощью алгоритма симметричного обхода, который даст вам дерево, повёрнутое на 90 градусов против часовой стрелки:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 void structure_r (struct node *root, int level)
2 {
3 int i;
4
5 if (root == NULL) {
6 for (i = 0; i < level; i++)
7 putchar ('\t');
8 puts ("~");
9 }
10 else {
11 structure_r (root->link [1], level + 1);
12
13 for (i = 0; i < level; i++)
14 putchar ('\t');
15 printf ("%d\n", root->data);
16
17 structure_r (root->link [0], level + 1);
18 }
19 }
20
21 void structure (struct tree *tree)
22 {
23 structure_r (tree->root, 0);
24 }</pre></span><p align="justify">Заметьте, что в принципе, код очень похож на функцию inorder() за исключением того, что помимо вывода значений узлов, мы ещё добавляем символы табуляции, число которых соответствует уровню узла в дереве. Мы также обозначаем на нашей импровизированной схеме листья, чтобы вы были уверенны в правильном построении дерева. Хотя рекурсивные алгоритмы и похожи на детские игрушки, они могут быть весьма мощным инструментом, если использовать их творчески. Подумайте, что ещё можно с ними сотворить :-)</p><p align="justify">Несмотря на эти чудесные решения, реализация нерекурсивных алгоритмов обхода может оказаться полезной. Такие алгоритмы сложнее в реализации из-за двойных рекурсивных вызовов. Если вы из тех, кто любит забегать вперёд, чтобы испытать или обхитрить учителя (позор!), вы могли отметить сходство обхода в прямом порядке и обхода по уровням, если очередь будет заменена на стек. Если нет, не волнуйтесь. Хотите верьте, хотите нет, но это всё, что необходимо сделать для создания алгоритма итеративного обхода в прямом порядке:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 void preorder (struct tree *tree)
2 {
3 struct node *it = tree->root;
4 struct node *up [50];
5 int top = 0;
6
7 if (it == NULL)
8 return;
9
10 up [top++] = it;
11
12 while (top != 0) {
13 it = up [--top];
14
15 printf ("%d\n", it->data);
16
17 if (it->link [1] != NULL)
18 up [top++] = it->link [1];
19 if (it->link [0] != NULL)
20 up [top++] = it->link [0];
21 }
22 }</pre></span><p align="justify">Симметричный обход сложнее. Нам надо переходить влево, не теряя из виду правые ссылки или родительские узлы. Это подразумевает как минимум несколько циклов - один для сохранения обратных ссылок, один для посещения сохранённых ссылок и другой для работы с последовательными ветками. К счасть, хоть логика и кажется сложной, код на удивление прост:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 void inorder (struct tree *tree)
2 {
3 struct node *it = tree->root;
4 struct node *up [50];
5 int top = 0;
6
7 while (it != NULL) {
8 while (it != NULL) {
9 if (it->link[1] != NULL)
10 up [top++] = it->link [1];
11
12 up [top++] = it;
13 it = it->link [0];
14 }
15
16 it = up [--top];
17
18 while (top != 0 && it->link [1] == NULL) {
19 printf ("%d\n", it->data);
20 it = up [--top];
21 }
22
23 printf ("%d\n", it->data);
24
25 if (top == 0)
26 break;
27
28 it = up [--top];
29 }
30 }</pre></span><p align="justify">Внешний цикл работает, пока указатель не равен NULL. Это может быть в случае, если дерево пустое или в стеке больше нет узлов. Вы увидите, что последняя строка внешнего цикла аккуратна с присвоением значения NULL, если стек пуст, так что алгоритм, собственно, завершается. Первый внутренний цикл имеет дело с сохранением правых ссылок родительских узлов при спуске вниз по левым ссылкам. Второй внутренний цикл посещает родительские узлы. Ну и наконец последний printf() показывает нам правые ссылки. Диаграмма выполнения построена на том же дереве, что и рекурсивный симметричный обход.</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>save 7, stack = { 7 }
save 5, stack = { 5, 7 }
save 3, stack = { 3, 5, 7 }
save 2, stack = { 2, 3, 5, 7 }
visit 2, stack = { 3, 5, 7 }
visit 3, stack = { 5, 7 }
visit 5, stack = { 7 }
pop 7, stack = {}
save 8, stack = { 8 }
save 7, stack = { 7, 8 }
save 6, stack = { 6, 7, 8 }
visit 6, stack = { 7, 8 }
visit 7, stack = { 8 }
pop 8, stack = {}
save 8, stack = { 8 }
visit 8, stack = {}</pre></span><p align="justify">Самый сложный случай - нерекурсивный обход в глубину в обратном порядке. Сложность состоит в необходимости придумать способ, как посетить узлы на низком уровне, сохраняя родителей и посещая их своевременно. Я неизбежно прихожу к использованию стека и вспомогательных счётчиков, причём 0 означает "сохранить левую ссылку", 1 - "сохранить правую ссылку", а 2 - "посетить вершину стека". Решение удобно, потому что оно хорошо вписывается в мою схему использовать булевы значения при вычислении условий для определения направления - правое или левое:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 void postorder (struct tree *tree)
2 {
3 struct {
4 struct node *p;
5 int n;
6 } up [50], it;
7 int top = 0, dir;
8
9 up [top].p = tree->root;
10 up [top++].n = 0;
11
12 while (top != 0) {
13 it = up [--top];
14
15 if (it.n != 2) {
16 dir = it.n++;
17 up [top++] = it;
18
19 if (it.p->link [dir] != NULL) {
20 up [top].p = it.p->link [dir];
21 up [top++].n = 0;
22 }
23 }
24 else
25 printf ("%d\n", it.p->data);
26 }
27 }</pre></span><p align="justify">Код короткий, но крайне непрозрачный. Диаграмма ниже может очень сильно помочь в попытке понять, что на самом деле делает алгоритм. Клянусь, всё не так сложно, как выглядит!</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>push 5:0, stack = { 5:0 }
increment, stack = { 5:1 }
push 3:0, stack = { 3:0, 5:1 }
increment, stack = { 3:1, 5:1 }
push 2:0, stack = { 2:0, 3:1, 5:1 }
increment, stack = { 2:1, 3:1, 5:1 }
increment, stack = { 2:2, 3:1, 5:1 }
visit 2:2, stack = { 3:1, 5:1 }
increment, stack = { 3:2, 5:1 }
visit 3:2, stack = { 5:1 }
increment, stack = { 5:2 }
push 7:0, stack = { 7:0, 5:2 }
increment, stack = { 7:1, 5:2 }
push 6:0, stack = { 6:0, 7:1, 5:2 }
increment, stack = { 6:1, 7:1, 5:2 }
increment, stack = { 6:2, 7:1, 5:2 }
visit 6:2, stack = { 7:1, 5:2 }
increment, stack = { 7:2, 5:2 }
push 8:0, stack = { 8:0, 7:2, 5:2 }
increment, stack = { 8:1, 7:2, 5:2 }
increment, stack = { 8:2, 7:2, 5:2 }
visit 8:2, stack = { 7:2, 5:2 }
visit 7:2, stack = { 5:2 }
visit 5:2, stack = {}</pre></span><p align="justify">Для алгоритма левостороннего обхода дерево представляет собой стек уровней, при этом каждый уровень состоит из узлов, находящихся на одной высоте. Такой алгоритм движется по всем узлам уровня прежде чем перейти на следующий уровень. Наиболее общая реализация начинает обход с вершины (корня) и обходит каждый уровень слева направо. Например, в следующем дереве левосторонний обход посетит узлы в такой последовательности: 5, 3, 7, 2, 6, 8. Заметьте, что это не единственный способ проделать левостронний обход. Это просто наиболее распространённая реализация такого обхода. Мы используем этот способ только в качестве иллюстрации.</p><div align="center"><img alt="bt11" src="data:image/gif;base64,R0lGODlhhgByAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///ywAAAAAhgByAAAI/wAfCBxIsKDBgwgTKkQIoKHDhRAjSpxIsaLFhA8LZrzIsaPHjxEbQhQJsqTJkxIBUFSJsqXLkyxXvpxJs2JMmzVz6jR402LPnUBb/vQZtKhQkEONKsVZMunSpwuTOiQZFarViVKZXt3KEKNWrmAHZiUatqzUqVjLmu0qNqXasE4JxpX7FuxcgXcf5K0LdOjNvHv56jxLtargrYENH76amO1ixkgfc22sUbJdjpQt02xcWLNVlYkze37JsvNBkaJHw9RoemNq1U0ZTvULe+nr17Uv4tabO+ju3b1DmgQeXCFx4sVPo0SevO3y5lhnO14NHWPrwsyd85Re2/R2vDP/Xv/PPngleefeWVumfF5s6Mei2/P++vY2acx8fx/tKN+jfur+qeXXRiMByJ+APNElXGwMXmacWw3KxZ1iiBW4YGQPXlihbO99dNde/dGnHIQBZkgiZBSmqJuKJqLYIosiKpjWZCOCp+GBXsnkIGvp1Viijze66OFw0wX5GZERyngijUMiiSFcTya5Yn0/OokjlVPuh59g7G225WEdehljXT26t1OY8Ok1XlFl4hWilmqipRRo47353FoSyjnfegguyWWffpKJ5YxgAkpofoPqiCieZAlq6JhC7pglo1BWySSlX0qqaaaRXtrkho9a+pSdkE46qqMG2gbVbDGRRxiB/En/RyqP27WZKaxx6rambx+66uZIgdlqq5QvNoVmjoHeqaiVyWp3KJzLfgppfPddKWq0NnIapbalLvkfs6Y2au2e414b7rNXHgcut81q9Wq72Lo3bGrvXkibs8iaS+6H7P61IGBGiltZt0U6CyK5+RYrsJLwFpwtwl4BHHC84OHqsIWnWdwVvw1j/N3DCSfrr3BzHXvuwxwvjPK/CofcL8NAxjuyYmOh+zK+Mds8s4kD6tmyym7OW27QB4M8NLsU20zft8QSDGO0TDfdsccq2+cStetOjHW1SSPtMsxUX9310RcPPLWHvW6Wdkpr97VrTrhqHNXbc07Ipt2x+lydTgEBADs=" /></div><p align="justify">Левосторонний обход, вероятно, один из немногих алгоритмов на бинарных деревьях поиска, который не может быть реализован рекурсивно без блуждания всеми самыми неудобными и трудными путями. Те, кто уже хорошо знаком с рекурсией, знают, что рекурсию можно подменить использованием стека. Левосторонний обход, однако, требует очереди, поэтому рекурсия здесь не очень практична.</p><p align="justify">В теории сам по себе алгоритм крайне прост и он следует той же логике, что и алгоритм обхода в прямом порядке, за исключением того, что здесь используется очередь. Правая и левая ссылки каждого узла добавляются в очередь, затем посещается сама запись (посещение - это просто выполнение какой-либо операции на узле, например, вывод на экран его данных). Следующий узел, который должен быть посещён, находится взятием из очереди первого элемента. Ниже приводится диаграмма левостороннего обхода на дереве, показанном выше:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>save 5, queue = { 5 }
visit 5, queue = {}
save 3, queue = { 3 }
save 7, queue = { 7, 3 }
visit 3, queue = { 7 }
save 2, queue = { 2, 7 }
visit 7, queue = { 2 }
save 6, queue = { 6, 2 }
save 8, queue = { 8, 6, 2 }
visit 2, queue = { 8, 6 }
visit 6, queue = { 8 }
visit 8, queue = {}</pre></span><p align="justify">Как только вы поймёте, что происходит, алгоритм станет для вас коротким и красивым. В принципе, это обход в прямом порядке с очередью вместо стека. Я использовал простой массив, как основу для вращающейся очереди с индексом для первого и последнего элемента. В целом, если отвлечься от деталей, функция действительно простая:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 void levelorder (struct tree *tree)
2 {
3 struct node *it = tree->root;
4 struct node *q [50];
5 int front = 0, back = 0;
6
7 if (it == NULL)
8 return;
9
10 q [front++] = it;
11
12 while (front != back) {
13 it = q [back++];
14
15 printf ("%d\n", it->data);
16
17 if (it->link [0] != NULL)
18 q [front++] = it->link [0];
19 if (it->link [1] != NULL)
20 q [front++] = it->link [1];
21 }
22 }</pre></span><p align="justify">Так как эта статься посвящена не очередям, а деревьям, я не буду пояснять, как работает наша очередь и вы можете просто поверить, что она работает. Или же вы можете протестировать мой код, чтобы убедиться, что я не пытаюсь отвлечь ваше внимание. Тем более, что я тоже допускаю ошибки, поэтому никогда не повредит проверить дважды :-)</p><p align="justify">Все эти обходы хороши для того, что они делают, но обычно не очень хороши для того, чего хотим мы. По крайней мере, они не делают того, что хочу я. А я хочу вот чего:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 int *x = first (tree);
2
3 while (x != NULL) {
4 printf ("%d\n", *x);
5 x = next (tree);
6 }</pre></span><p align="justify">Это сложно проделать с рекурсивными и нерекурсивными алгоритмами, которые мы рассмотрели, потому что где-то надо сохранять состояние и последний шаг, сделанный алгоритмом. Самый простой выход из этой ситуации - хранить информацию о состоянии обхода в отдельной структуре обхода, которая бы содержала необходимую информацию. Так как нам нужно сохранять узла, идущие дальше по дереву, нам нужен стек, как например в алгоритме обхода в глубину. Нам нужно отслеживать, какой у нас текущий узел. Мы будем использовать такую структуру (размер стека произволен, но он должен быть не меньше, чем высота предполагаемого дерева):</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 struct traversal {
2 struct node *up [50]; /* Stack */
3 struct node *it; /* Current node */
4 int top; /* Top of stack */
5 };</pre></span><p align="justify">Т.к. самый распространённый алгоритм обхода - симметричный, мы будем использовать его. Это никоим образом не единственный алгоритм, который можно осуществлять пошагово, но доступ к данным у порядоченном виде пожалуй делает его самым полезным, тем более, что добавление операций продвижения вперёд и назад при использовании этого алгоритма доволно просто. Чтобы начать симметричный обход, нам сперва надо найти узел с наименьшим значением или самый дальний левый узел. Напишем простую функцию first(), которая будет инициализировать структуру traversal и установит в качестве текущего узла самый младший узел, сохраняя путь к нему:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int *first (struct traversal *trav, struct tree *tree)
2 {
3 trav->it = tree->root;
4 trav->top = 0;
5
6 if (trav->it != NULL) {
7 while (trav->it->link [0] != NULL) {
8 trav->up [trav->top++] = trav->it;
9 trav->it = trav->it->link [0];
10 }
11 }
12
13 if (trav->it != NULL)
14 return &trav->it->data;
15 else
16 return NULL;
17 }</pre></span><p align="justify">Обратите внимание - возвращается не само значение, указатель на него. Это упрощает проверку границ, ведь когда обход закончен, мы может возвратить указатель на NULL. Это хорошо вписывается в мои желания, которые я упомянул выше. first() - простая функция и мы можем превратить её в last(), заменив 0 на 1, чтобы изменить направление движения на правое вместо левого.</p><p align="justify">Теперь сложная часть. Пошаговый обход нам нужно проводить начиная с самого младшего узла. Код относительно простой. Если текущий код имеет правую ссылку, мы находим порядкового преемника (старший узел, чьё значение больше текущего) ниже по дереву и обновляем стек соответственно. Если у текущего узла нет правой ссылки, мы ищем старший узел выше по дереву. При этом из стека извлекаются узлы, пока мы не посетим узел по правой ссылке. Если стек пуст, мы завершаем обход и устанавливаем указатель на текущий узел в NULL.</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int *next (struct traversal *trav)
2 {
3 if (trav->it->link [1] != NULL) {
4 trav->up [trav->top++] = trav->it;
5 trav->it = trav->it->link [1];
6
7 while (trav->it->link [0] != NULL) {
8 trav->up [trav->top++] = trav->it;
9 trav->it = trav->it->link [0];
10 }
11 }
12 else {
13 struct node *last;
14
15 do {
16 if (trav->top == 0) {
17 trav->it = NULL;
18 break;
19 }
20
21 last = trav->it;
22 trav->it = trav->up [--trav->top];
23 } while (last == trav->it->link [1]);
24 }
25
26 if (trav->it != NULL)
27 return &trav->it->data;
28 else
29 return NULL;
30 }</pre></span><p align="justify">Теперь-то я могу делать всё, что захочу, минимально изменив код, а всё, что нам для этого понадобилось, это немного помыслить пошаговый обход, переписав код нерекурсивного обхода. Вы рады, что я сделал это для вас? :-)</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 struct traversal it;
2 int *x = first (&it, tree);
3
4 while (x != NULL) {
5 printf ("%d\n", *x);
6 x = next (&it);
7 }</pre></span><p align="justify">Вот и всё. Пошаговый обход оказался ненамного сложнее нерекурсивного обхода, да к тому же он настолько гибче, что это даже не смешно. Вы можете сделать и другие типы обхода пошаговыми, но как я сказал ранее, симметричный обход самый распространённый. Поразвлекайтесь с нерекурсивными обходами и посмотрите, как сделать их пошаговыми. Если сможете это сделать, я могу сказать спокойно, что вы понимаете концепцию :-)</p><a name="bst_section6"></a><h4>Родительские указатели <a href="#top">[наверх]</a></h4><p align="justify">Самая большая проблема возникает при работе с бинарными деревьями, когда вам нужно сделать что-то, требующее подъёма вверх по дереву, например, чтобы совершить обход (или балансировку). Нам нужно использовать либо явный стек, в котором будет храниться путь, или неявный в виде рекурсии. Но иногда есть смысл поступить умнее. Используя для этих целей рекурсию мы можем упереться в некий предел, а определить этот предел приемлемым способом, который был бы легко адаптируемым, нельзя. Используя явный стек, мы задавали произвольную глубину стека, т.к. мы использовали не расширяющийся стек.</p><p align="justify">Теперь вы можете использовать какую-либо хорошую библиотеку для работы со стеком или придумать некий иной механизм, который позволил бы двигаться в обратном направлении вверх по дереву. Как оказывается, последнее решение более распространено, чем первое и почти все распространённые реализации используют дополнительные ссылки, указывающие на родителя, для каждого узла. Эти ссылки называются родительскими указателями:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 struct node {
2 int data;
3 struct node *up;
4 struct node *link [2];
5 };
6
7 struct tree {
8 struct node *root;
9 };</pre></span><p align="justify">Вставка узла в дерево с родительскими указателями довольно проста. Нужно добавить совсем немного кода, чтобы адаптировать нашу функцию вставки для корректной работы с родительскими указателями. Однако необходимо позаботиться о назначении листу родительского указателя на корень, так, чтобы мы могли проверить его. Всё это выполняется функцией make_node(), которая выделяет память под новый узел, присваивает данные и устанавливает все ссылки в NULL.</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int insert (struct tree *tree, int data)
2 {
3 if (tree->root == NULL)
4 tree->root = make_node (data);
5 else {
6 struct node *it = tree->root;
7 int dir;
8
9 for ( ; ; ) {
10 dir = it->data < data;
11
12 if (it->data == data)
13 return 0;
14 else if (it->link [dir] == NULL)
15 break;
16
17 it = it->link [dir];
18 }
19
20 it->link [dir] = make_node (data);
21 it->link [dir]->up = it;
22 }
23
24 return 1;
25 }</pre></span><p align="justify">Родителем нового узла является узел, адрес которого содержит it, здесь у нас нет проблем с установкой ссылок. До тех пор, пока вы можете получить доступ к родительскому узлу, родительские указатели тривиальны. Хотя, конечно, это усложняет рекурсивные решения по сравнению с вариантами без родительских указателей.</p><p align="justify">Удаление узла из дерева с родительскими указателями также немного сложнее. Нам нужна дополнительная переменная для хранения адреса родительского узла. Особое внимание надо обратить на обновление родительских ссылок узла, ведь возможно, что заменяющий узел будет листовым! Это может произойти с корнем дерева и далее вниз по дереву, поэтому такие ситуации мы рассматриваем, как особые случаи:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int remove (struct tree *tree, int data)
2 {
3 if (tree->root != NULL) {
4 struct node head = { 0 };
5 struct node *it = &head;
6 struct node *f = NULL;
7 int dir = 1;
8
9 it->link [1] = tree->root;
10 tree->root->up = &head;
11
12 while (it->link [dir] != NULL) {
13 it = it->link [dir];
14 dir = it->data <= data;
15
16 if (it->data == data)
17 f = it;
18 }
19
20 if (f != NULL) {
21 int dir = it->link [0] == NULL;
22
23 f->data = it->data;
24 it->up->link [it->up->link [1] == it] =
25 it->link [dir];
26
27 if (it->link [dir] != NULL)
28 it->link [dir]->up = it->up;
29
30 free (it);
31 }
32
33 tree->root = head.link [1];
34 if (tree->root != NULL)
35 tree->root->up = NULL;
36 }
37
38 return 1;
39 }</pre></span><p align="justify">Наконец, мы подошли к обходу. Ради этого и затевалась вся игра с родительскими указателями. Теперь для перехода к старшему узлу вверх по дереву нам не нужен хитрый стек, достаточно пройти по родительским указателям. struct traversal и first() почти не изменились и всё отличие состоит в том, что здесь не используется стек:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 struct traversal {
2 struct node *it;
3 };
4
5 int *first (struct traversal *trav, struct tree *tree)
6 {
7 trav->it = tree->root;
8
9 if (trav->it != NULL) {
10 while (trav->it->link [0] != NULL)
11 trav->it = trav->it->link [0];
12 }
13
14 if (trav->it != NULL)
15 return &trav->it->data;
16 else
17 return NULL;
18 }</pre></span><p align="justify">Функция next() содержит больше отличий. Здесь мы, отказавшись от стека, в зависимости от необходимости можем двигаться вверх, используя родительские указатели, просто следуя по ссылкам, содержащимся в них. В принципе, логика осталась такой же и изменения минимальны. Неплохая цена за отказ от стека, не так ли?</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int *next (struct traversal *trav)
2 {
3 if (trav->it->link [1] != NULL) {
4 trav->it = trav->it->link [1];
5
6 while (trav->it->link [0] != NULL)
7 trav->it = trav->it->link [0];
8 }
9 else {
10 for ( ; ; ) {
11 if (trav->it->up == NULL || trav->it == trav->it->up->link [0])
12 {
13 trav->it = trav->it->up;
14 break;
15 }
16
17 trav->it = trav->it->up;
18 }
19 }
20
21 if (trav->it != NULL)
22 return &trav->it->data;
23 else
24 return NULL;
25 }</pre></span><a name="bst_section7"></a><h4>Правовинтовые деревья <a href="#top">[наверх]</a></h4><p align="justify">Родительские указатели во многих ситуациях полезны, но они требуют внесения некоторой избыточности, которая не везде и не всегда может оказаться приемлемой. Ввиду этой проблемы было придумано очень умное решение, где листья дерева могут использоваться определённым образом, указывая не на NULL, а на старшие и младшие узлы внешних узлов. Это называется винтовым деревом (или резьбовым). Теперь у нас нет избыточности дополнительных указателей, а вместо этого мы добавляем флаг, который определяет, какую ссылку содержит узел - реальную или резьбовую. Зачем флаг? Мы попали бы в бесконечный цикл, если бы не смогли отличить резьбовую ссылку от обычной. На каждую ссылку в дереве нам нужен флаг. Полные резьбовые деревья требуют 2 флагов (<i>Здесь ваш покорный слуга вновь столкнулся с терминолическими проблемами и, в частности, с отсутствием однозначного соответствия английским терминам в русскоязычной литературе. Поневоле создаётся впечатление, что эти разновидности деревьев либо называют исключительно по-английски, либо... Их вообще никак не называют. Посему, я взял в качестве русского эквивалента термина threaded binary search tree выражение "резьбовое бинарное дерево поиска". Продолжая аналогию с резьбой, right threaded BST я назвал правовинтовым деревом. Просто имейте ввиду, что когда речь идёт о резьбе, или правовинтовых деревьях, подразмевается по существу одно и то же - резьбовые деревья</i> - перев.) Наиболее распространённое решение использует только связь через правые ссылки, оставляя левые ссылки такими же, как в обычном дереве:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 struct node {
2 int data;
3 int thread;
4 struct node *link [2];
5 };
6
7 struct tree {
8 struct node *root;
9 };</pre></span><p align="justify">Следующая диаграмма построена с применением упомянутой структуры. Каждая правая ссылка, которая указывала бы на лист, теперь указывает на старший узел, в то время, как левая ссылка указывает на обычный лист. Обратите внимание, что теперь мы можем напрямую пройти от узла 4 к узлу 6 вместо того, чтобы прокладывать себе путь через узел 3 при использовании родительских указателей или стека. Это первое преимущество резьбовых деревьев и вскоре вы увидите, что такие пути значительно упрощают пошаговые обходы. Также обратите внимание, как поиск узла 5 обошёл бы по кругу узлы 6 - 3 - 4, возратившись обратно к узлу 6. Вообще говоря, это нежелательно, потому мы используем флаг.</p><div align="center"><img alt="bt12" src="data:image/gif;base64,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" /></div><p align="justify">Резьбовые деревья не очень распространены, и наиболее распространённые их реализации допускают использование только левых ссылок, позволяя совершать симметричный обход, посещающий узлы в отсортированном по возрастанию порядке. Для обхода в убывающем порядке нам нужно использовать одно из других решений (родительские указатели, рекурсия или стек). Такие деревья называются правовинтовыми, потому что используется объединение в цепь по правой стороне. Код для работы с такими деревьями более сложен, чем в случае с родительскими указателями. Алгоритм поиска должен быть изменён для работы с резьбой (потоком - нитью). Если мы достигаем нити, это всё равно, как если бы мы достигли листа. Так мы избегаем не очень приятной проблемы с вечным циклом:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int find (struct tree *tree, int data)
2 {
3 struct node *it = tree->root;
4 int dir;
5
6 for ( ; ; ) {
7 dir = it->data < data;
8
9 if (it->data == data)
10 return 1;
11 else if (dir == 1 && it->thread == 1)
12 break;
13 else if (it->link [dir] == NULL)
14 break;
15
16 it = it->link [dir];
17 }
18
19 return 0;
20 }</pre></span><p align="justify">Т.к. симметрия нашего дерева нарушена, теперь нам необходимо учитывать разницу между движением влево (где мы можем найти лист, но не нить) и вправо (где мы можем найти нить, но не лист). При вставке поиск ведётся так же, но непосредственно при добавлении узла алгоритм должен учитывать ассимметрию. Если узел добавляется к правой ссылке, он должен принять от своего родителя нить. Если к левой ссылке, то нить передаётся родительскому узлу. Рассмотрим вставку узла 5 в резьбовое дерево, приведённое выше. 5 будет добавлено, как правая ссылка узла 4. Но правая ссылка узла 4 - нить. Чтобы обеспечить правильное построение резьбового дерева, нам нужно сместить нить вниз так, чтобы правая ссылка узла 5 указывала на узел 6. С другой стороны, если бы мы вставляли 0, то новый узел был бы присоединён к левой ссылке узла 2. Однако нам бы всё же пришлось установить правую нить узла 0 так, чтобы она ссылалась на старший узел - 2:</p><div align="center"><img alt="bt13" src="data:image/gif;base64,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" /></div><p align="justify">До тех пор, пока вы придерживаетесь этих правил для операции вставки, построение резьбового дерева - лёгкая работа, т.к. изменения, которые нужно вносить в дерево, носят локальный характер и вам не приходится обрабатывать всю структуру. Код, который это делает, не особо сложен, но он может вводить в заблуждение, если вы незнакомы с резьбовыми деревьями. В частности, кое-что этот код делает иначе для правых и левых ссылок. make_node() всё так же захватывает память, присваивает данные, устанавливает ссылки в NULL, но для резьбового дерева эта функция также устанавливает флаг в 1, потому что новый узел всегда будет внешним и без потомков. Заметьте, что самый правый узел в дереве имеет нить, ведущую к листу. Это может повлиять на другие операции на дереве. Вот и сам код:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int insert (struct tree *tree, int data)
2 {
3 if (tree->root == NULL)
4 tree->root = make_node (data);
5 else {
6 struct node *it = tree->root, *q;
7 int dir;
8
9 for ( ; ; ) {
10 dir = it->data < data;
11
12 if (it->data == data)
13 return 0;
14 else if (dir == 1 && it->thread == 1)
15 break;
16 else if (it->link [dir] == NULL)
17 break;
18
19 it = it->link [dir];
20 }
21
22 q = make_node (data);
23
24 if (dir == 1) {
25 q->link [1] = it->link [1];
26 it->thread = 0;
27 }
28 else
29 q->link [1] = it;
30
31 it->link [dir] = q;
32 }
33
34 return 1;
35 }</pre></span><p align="justify">Вместо непосредственного присвоения нового узла напрямую родителю, мы сохраняем его во временной переменной. Таким образом текущая нить родительского узла не теряется, если новый узел должен стать правой ссылкой родителя и процесс добавления нового узла в резьбовое дерево вовсе не так сложен, если вы понимаете идею :-)</p><p align="justify">Удаление узла из резьбового дерева следует тому же шаблону, но здесь у нас есть 4 случая изъятия узла из дерева. Если узел, который мы хотим удалить, не имеет потомков и является левым потомком своего родителя, мы можем просто изменить левую ссылку родителя, чтобы она указывала на лист. Если наш узел - правый и не имеет дочерних узлов, нам нужно установить правую ссылку его родителя в то же значение, которое содержит правая ссылка нашего удаляемого узла, чтобы сохранить нить.</p><div align="center"><img alt="bt14" src="data:image/gif;base64,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" /></div><p align="justify">Если узел, который мы хотим удалить, имеет потомков (это внешний узел, поэтому у него будет только один дочерний узел) и этот потомок - правый узел, мы просто заменяем удаляемый узел этим потомком, т.к. изменений в пути нити вносить не нужно. С другой стороны, если потомок слева, нам нужно передать нить удаляемого узла дочернему узлу, прежде чем мы заменим удаляемый узел его потомком. Здесь есть небольшая хитрость, но она стоит того, чтобы применить её в пошаговом обходе:</p><div align="center"><img alt="bt15" src="data:image/gif;base64,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" /></div><p align="justify">Код для выполнения удаления не очень сложен, но сперва он может показаться запутанным. Как обычно, мы используем элегантное удаление заменой узла, потому что оно короче и красивее. Красота - это хорошо. Я очень-очень серьёзно подхожу к моему коду с эстетических позиций. Конечно, это не значит, что я так же очень-очень серьёзно не подхожу к правильности кода ;-)</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int remove (struct tree *tree, int data)
2 {
3 if (tree->root != NULL) {
4 struct node head = { 0 };
5 struct node *it = &head;
6 struct node *q, *p, *f = NULL;
7 int dir = 1;
8
9 it->link [1] = tree->root;
10
11 while (it->link [dir] != NULL) {
12 if (dir == 1 && it->thread == 1)
13 break;
14
15 p = it;
16 it = it->link [dir];
17 dir = it->data <= data;
18
19 if (it->data == data)
20 f = it;
21 }
22
23 if (f != NULL) {
24 q = it->link [it->link [0] == NULL];
25 dir = p->link [1] == it;
26 f->data = it->data;
27
28 if (p == q)
29 p->link [0] = NULL;
30 else if (it->link [0] == NULL && it->thread) {
31 p->thread = 1;
32 p->link [1] = it->link [1];
33 }
34 else if (it->link[0] == NULL)
35 p->link [dir] = q;
36 else {
37 q->thread = it->thread;
38 q->link [1] = it->link [1];
39 p->link [dir] = q;
40 }
41
42 free (it);
43 }
44
45 tree->root = head.link [1];
46 }
47
48 return 1;
49 }</pre></span><p align="justify">Хорошо, но как эти условия соответствуют рассмотренным выше случаям? p - это родитель, а q - дочерний узел, которым мы заменяем родителя. Если p == q, то q - правый узел it и его нить к p. Это соответствует случаю, где мы на диаграмме удаляем узел 0. Если левая ссылка узла, который мы хотим удалить - лист, а правая - нить, то это соответствует тому случаю, когда мы удаляли узел 5. Если левая ссылка удаляемого узла равна NULL, а правая ссылка - не нить, то у нас случай с удалением узла 4. Ну и наконец последнее - случай с удалением узла 2 на диаграмме. Если вы хорошо понимаете, что происходит в каждом случае, то с пониманием кода у вас проблем возникнуть не должно.</p><p align="justify">Пошаговый обход очень упростился, потому что нам не нужно искать старший узел. Всё что нам нужно, так это пройти по ссылке. Вот и всё волшебство, которое было нам необходимо для движения вверх по дереву. Но это до тех пор, пока мы не дошли до правой стороны. Представленный алгоритм и является сакральной сутью симметричного обхода (ну, за исключением того, что иметь возможность двигаться в обоих направлениях в полностью резьбовом дереве было бы круче). Позор, что мы так долго шли к построению дерева, которое позволяет это:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 struct traversal {
2 struct node *it;
3 };
4
5 int *first (struct traversal *trav, struct tree *tree)
6 {
7 trav->it = tree->root;
8
9 if (trav->it != NULL) {
10 while (trav->it->link [0] != NULL)
11 trav->it = trav->it->link [0];
12 }
13
14 if (trav->it != NULL)
15 return &trav->it->data;
16 else
17 return NULL;
18 }
19
20 int *next (struct traversal *trav)
21 {
22 if (trav->it->thread == 0) {
23 trav->it = trav->it->link [1];
24
25 if (trav->it != NULL) {
26 while (trav->it->link [0] != NULL)
27 trav->it = trav->it->link[0];
28 }
29 }
30 else
31 trav->it = trav->it->link [1];
32
33 if (trav->it != NULL)
34 return &trav->it->data;
35 else
36 return NULL;
37 }</pre></span><p align="justify">Родительские указатели более распространены, чем резьбовые деревья просто потому, что на указателях проще представить, что происходит. Правовинтовые деревья - самая распространённая разновидность резьбовых деревьев, но вы можете реализовать и симметричновинтовое дерево, которое похоже на рассмотренные выше, но позволяет иметь нити для двух направлений. Логика этих деревьев достаточно схожа с рассмотренной нами. Мы рассмотрели только довольно общий вариант, чтобы свести к минимуму затраты на лечение моего туннельного синдрома, который я получу от написания этих статей ;-)</p><a name="bst_section8"></a><h4>Производительность <a href="#top">[наверх]</a></h4><p align="justify">Немного полезной теории касательно деревьев. Вам нужно знать, насколько хороши деревья на практике и для этого лучше обратиться к анализу, проведённому людьми, которые умнее, чем я. Если операции удаления и вставки случайны, то средняя эффективность дерева <i>O(log N)</i>, или логарифм по основанию 2, или число степеней 2, чтобы добраться до N. И на самом деле, это хорошо, кстати. Если эффективность поиска <i>O(log N)</i>, то на каждом последующем шаге поиска диапазон вариантов уменьшается где-то наполовину. Поэтому поиск в дереве очень быстрый, если операции вставки и удаления случайны.</p><p align="justify">Заметьте, я говорю "если вставка и удаление случайны". Таким образом я прикрываюсь от нападок тех дотошных личностей, которые готовы были бы меня сжечь на костре инквизиции, если бы я высказал своё мнение, как абсолютное. На самом деле для бинарных деревьев поиска есть действительно ужасный случай, когда каждый узел дерева - внешний. Самый простой способ получить такое дерево - вставка упорядоченной по возрастанию последовательности:</p><div align="center"><img alt="bt16" src="data:image/gif;base64,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" /></div><p align="justify">Вместо структуры с эффективностью <i>O(log N)</i> мы, в принципе, получили связный список, а производительность упала до <i>O(N)</i>. Такой случай называют вырожденным деревом и он проявляется при крайне неблагоприятных последовательностях операций вставки и удаления, которые невозможно предсказать. Это не способствует надёжности деревьев. Дерево, которое пытается противостоять таким случаям, исправляя ситуацию, называется сбалансированным. Все деревья, рассмотренные в этой статье, несбалансированные, поэтому всё, что в ваших силах - убедиться, что вставка и удаления будут производиться случайно. Это уменьшит вероятность вырождения дерева.</p><p align="justify">Есть разные способы гарантировать близкую к оптимальной производительность либо при каждой операции, либо после последовательности таких операций, но эти способы могут быть очень сложными. Я написал руководства по наиболее распространённым таким способам, поэтому здесь я не буду снова повторять самого себя. Можно также полностью перебалансировать дерево. Например, копировать все узлы дерева в массив, затем выполнять по массиву что-то, вроде бинарного поиска, рекурсивно выбирая средний узел и вставляя его в новое дерево. В результате мы получим хорошо сбалансированное дерево. После такой перебалансировки вырожденное дерево, подобное тому, что приведено выше, примет такой вид:</p><div align="center"><img alt="bt17" src="data:image/gif;base64,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" /></div><p align="justify">Ещё лучше, что эту схему легко представить и реализовать, но она неэффективна. Другое способ - изменять структуру на самую худшую, как показано ниже (только временно, разумеется!), а затем выполнить операцию, называемую поворотом через каждый узел. Реализацию я оставлю вам (гуглите DSW, если хотите), а сам процесс будет выглядеть так:</p><div align="center"><img alt="bt18" src="data:image/gif;base64,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" /></div><p align="justify">Глобальная перебалансировка дерева довольно затратная операция, потому что её эффективность по крайней мере <i>O(N)</i>, так что, если вы хотите обеспечивать структуру дерева таким способом, откладывайте перебалансировку как можно дольше :-) В случаях, когда вероятность вырождения дерева высока, используются разные схемы балансировки (такие, как AVL или красно-чёрные деревья), которые обеспечивают баланс за счёт локальных изменений. Это лучший выход, потому что сложность массовой перебалансировки всего дерева распределяется между отдельными операциями. Такой подход эффективнее, чем одна неэффективная массовая операция.</p><p align="justify">Окончательный вывод: эффективность бинарных деревьев поиска в среднем для дерева из N узлов составляет <i>O(log N)</i> и <i>O(N)</i> в худшем случае, что делает несбалансированное дерево не таким полезным, сбалансированное со средней гарантированной эффективностью <i>O(log N)</i>. Но если известно, что данные на входе поступают в случайном порядке, небалансируемое дерево легче в реализации и поддержании..</p><a name="bst_section9"></a><h4>Заключение <a href="#top">[наверх]</a></h4><p align="justify">Уф! Кто знал, что бинарные деревья поиска такие разносторонние? Я знал, а теперь знаете и вы. Темы, касающиеся деревьев, весьма многочисленны. Я бы мог забить статьями об этом всю квоту на хостинге. Однако, большая часть этого всего не имеет практической пользы в реальной жизни, либо интересно только интеллектуалам, которые сами не пишут реального кода. Я охватил те темы, которые позволят вам заработать ваши <название местной валюты> и те, которые в состоянии реализовать обычный человек. Как это часто бывает со структурами данных, вы можете усложнить их, насколько угодно и некоторые из балансирующихся деревья именно это и делают.</p><p align="justify">Это руководство (надеюсь!) прояснило для вас, что такое бинарное дерево поиска и как реализовать такое дерево. Мы не просто прошлись по основам, но рассмотрели различные элегантные и эффективные вариации алгоритмов и трюки, призванные улучшить гибкость базовых бинарных деревьев поиска за счёт сокращения сложности обходов. И наконец, мы немного прошлись по теории (совсем немного) в той части, что касается производительности, познакомившись не только с преимуществами деревьев, но и с их недостатками.</p><p align="justify">Оригинал этой статьи вы можете найти здесь: <a href="http://eternallyconfuzzled.com">eternallyconfuzzled.com</a>.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-23399484188991437782011-10-17T09:31:00.000-07:002011-10-17T09:34:18.346-07:00Базисные деревья (Radix trees) в ядре Linux<p align="justify">Ядро включает в себя множество библиотечных функций для реализации полезных структур данных. Среди этих структур два типа деревьев: базисные деревья (radix trees или дерево остатков в некоторых источниках) и красно-чёрные деревья. Данная статья даёт обзор API для работы с базисными деревьями.</p><p align="justify">В Википедии есть <a href="http://en.wikipedia.org/wiki/Radix_tree">статья</a> о базисных деревьях, но применительно к базисным деревьям Linux описание не очень хорошее. Базисные деревья Linux являются механизмом, который сопоставляет (указатель) значение целочисленному ключу (тип long). Этот механизм эффективен, как хранилище и предоставляет достаточно быстую операцию поиска. Кроме того, базисные деревья Linux имеют некоторые свойства, обеспечиваемые ядром, включая тэги для конкретных вхождений.</p><p align="justify">Диаграмма чуть ниже изображает листовой узел базисного дерева Linux.</p><div align="center"><img alt="" src="data:image/png;base64,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" /></div><p align="justify">Узел содержит множество слотов, каждый из которых может хранить указатель на какие-то данные в зависимости от замысла того, кто создал это дерево. Пустые слоты содержат указатели на NULL. Эти деревья довольно широкие - в ядрах 2.6.16-rc листья имеют по 64 слотов каждый. Слоты индексируются по части целочисленного ключа (тип long). Если максимальное значение ключа меньше 64, всё дерево может быть представлено одним единственным узлом. Однако, как правило используются большие множества ключей - в противном же случае в подобных целях может быть использован обычный массив. Большое дерево может выглядеть, как показано на рисунке:</p><div align="center"><img alt="" src="data:image/png;base64,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" /></div><p align="justify">Это дерево имеет глубину 3 уровня. Когда ядро ищет определённый ключ, для поиска нужного слота в корневом узле используются первые 6 значащих битов. Следующие 6 битов индексируют средний узел, а последние 6 битов будут указывать на слот, содержащий указатель на нужные данные. Узлы, не имеющие потомков, не представлены в дереве, таким образом базисное дерево предоставляет эффективное хранилище данных ввиде разреженных деревьев.</p><p align="justify">Так выглядят структуры корня (include/linux/radix-tree.h) и обычного узла (lib/radix-tree.c) базисного дерева:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>/*** radix-tree API starts here ***/
#define RADIX_TREE_MAX_TAGS 2
/* root tags are stored in gfp_mask, shifted by __GFP_BITS_SHIFT */
struct radix_tree_root {
unsigned int height;
gfp_t gfp_mask;
struct radix_tree_node *rnode;
};
#define RADIX_TREE_INIT(mask) { \
.height = 0, \
.gfp_mask = (mask), \
.rnode = NULL, \
}
#define RADIX_TREE(name, mask) \
struct radix_tree_root name = RADIX_TREE_INIT(mask)
#define INIT_RADIX_TREE(root, mask) \
do { \
(root)->height = 0; \
(root)->gfp_mask = (mask); \
(root)->rnode = NULL; \
} while (0)</pre></span>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
struct radix_tree_node {
unsigned int height; /* Height from the bottom */
unsigned int count;
struct rcu_head rcu_head;
void *slots[RADIX_TREE_MAP_SIZE];
unsigned long tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
};</pre></span><p align="justify">В основной версии ядра базисные деревья используются в нескольких местах. На архитектуре PowerPC они используются для отображения реальных и виртуальных номеров линий запроса прерывания (IRQ). Код NFS ставит в соответствие релевантному индексному узлу (inode) дерево для отслеживания запросов. Наиболее широко используются базисные деревья в подсистеме управления памятью. Структура address_space, используемая в страничном кэше ядра для хранения информации об устройстве резервной памяти и отображения страниц, содержит базисное дерево для отслеживания страниц, назначенных данному отображению. Кроме всего прочего, это дерево позволяет коду подсистемы управления памятью быстро находить грязные страницы и страницы в режиме обратной записи (dirty and writeback pages).</p><p align="justify">Как и в случаях с другими структурами данных ядра, есть два способа объявить и проинициализировать базисное дерево:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#include <linux/radix-tree.h>
RADIX_TREE(name, gfp_mask); /* Declare and initialize */
struct radix_tree_root my_tree;
INIT_RADIX_TREE(my_tree, gfp_mask);</pre></span><p align="justify">Первое - объявление и инициализация переменной базисного дерева с заданным именем; второе - инициализация дерева во время выполнения. В любом случае неоходимо указать значение gfp_mask, чтобы уведомить ядро, каким образом должна быть выделена память под дерево. Если операции базисного дерева, такие, как вставка, в частности, будут выполняться в атомарном контексте, маска должна содержать флаг GFP_ATOMIC.</p><p align="justify">Функции добавления и удаления записей:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>int radix_tree_insert(struct radix_tree_root *tree, unsigned long key,
void *item);
void *radix_tree_delete(struct radix_tree_root *tree, unsigned long key);</pre></span><p align="justify">Вызов функции radix_tree_insert() приведёт ко вставке новых данных (сопоставленных с ключом) в дерево. Операция вставки может потребовать выделения памяти; если выделение завершилось неудачей, операция вставки также завершается с кодом возврата -ENOMEM. Код вставки не допустит затирания существующих данных; если в дереве уже присутствует заданный ключ, radix_tree_insert() возвратит -EEXIST. В случае успеха код возврата равен нулю. radix_tree_delete() удаляет данные, сопоставленные с данным ключом дерева, возвращая указатель на эти данные, если он был.</p><p align="justify">Возможны ситуации, в которых ошибка вставки новых данных в базисное дерево стала бы значительной проблемой. Чтобы помочь избежать таких ситуаций предусмотрены две специальные функции:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>int radix_tree_preload(gfp_t gfp_mask);
void radix_tree_preload_end(void);</pre></span><p align="justify">Эта функция попытается выделить для дерева достаточно памяти (используя значение gfp_mask), чтобы гарантировать, что последующая операция вставки в базисное дерево не завершится неудачей. Структуры, под которые выделена память, хранятся в переменной, привязанной к процессору, а это значит, что вызывающая функция должна произвести вставку до перепланирования или миграции на другой процессор. Таким образом, в случае успеха radix_tree_preload() возвратит управление вызывающей функции с запретом вытеснения; впоследствии вызывающий код должен обеспечить разрешение вытеснения с помощью вызова функции radix_tree_preload_end(). В случае неудачи код возврата равен значению -ENOMEM, а вытеснение не запрещено.</p><p align="justify">Поиск в базисном дереве может осуществляться несколькими способами:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void *radix_tree_lookup(struct radix_tree_root *tree, unsigned long key);
void **radix_tree_lookup_slot(struct radix_tree_root *tree, unsigned long key);
unsigned int radix_tree_gang_lookup(struct radix_tree_root *root,
void **results,
unsigned long first_index,
unsigned int max_items);</pre></span><p align="justify">Самый простой способ - использование функции radix_tree_lookup(). Эта функция ищет в дереве ключ и возвращает ассоциированную с ним запись (или NULL при неудаче). radix_tree_lookup_slot() напротив возратит слот, содержащий необходимые данные. Вызывающий код затем может изменить ассоциированный с ключом указатель. Если запись не существовала на момент вызова функции radix_tree_lookup_slot() не создаст новый слот для указателя. В таких случаях для вставки новых данных необходимо использовать radix_tree_insert().</p><p align="justify">Наконец radix_tree_gang_lookup() возратит до max_items записей, отсортированных по ключам в возрастающем порядке, начиная со значения first_index. Число возвращаемых записей может быть меньше запрошенного, но меньшее значение (не нуль) не означает, что в дереве больше нет значений.</p><p align="justify">Необходимо также иметь ввиду, что при поиске или сортировке базисное дерево не держит блокировок само по себе. Задача обеспечения когерентности данных в дереве при работе с несколькими потоками полностью ложится на вызывающий код. Доступен патч Ника Пиджина (Nick Piggin), который добавляет поддержку механизма RCU при удалении узлов дерева; этот патч позволяет осуществлять поиск без блокировки, пока (1) результирующий указатель используется в атомарном контексте и (2) вызывающий код сам не способствует появлению ситуации гонок. Однако, неизвестно, когда патч будет принят в основную ветку ядра (<i>К сожалению, я не могу сказать точно, с какой версии, но патч уже принят и в ядре 2.6.30, например, RCU-синхронизация доступна в основной ветке. Очевидно, патч был принят уже довольно давно. Основной для этой статьи является <a href="http://lwn.net/Articles/175432/">сообщение на lwn.net</a>, датированное 2006 годом, поэтому такая неточность вполне закономерна</i>).</p><p align="justify">Базисное дерево поддерживает "тэги", которые позволяют устанавливать отдельные биты на записях, хранимых в дереве. В частности, тэги используются для маркировки грязных страниц и страниц в режиме обратной записи. API для работы с тэгами выглядит так:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void *radix_tree_tag_set(struct radix_tree_root *tree,
unsigned long key, int tag);
void *radix_tree_tag_clear(struct radix_tree_root *tree,
unsigned long key, int tag);
int radix_tree_tag_get(struct radix_tree_root *tree,
unsigned long key, int tag);</pre></span><p align="justify">radix_tree_tag_set() присвоит данный тэг (tag) записи, идентифицируемой ключом (key); попытка присвоить тэг по несуществующму ключу будет являться ошибкой. Возвращаемое значение в случае успеха - указатель на запись, помеченную тэгом. Тэг выглядит, как обычное целое число, а текущий код позволяет использование не более двух тэгов (<i>Как видно из кода, приведённого в самом начале статьи, это ограничение справедливо и сейчас. См. значение макроопределения RADIX_TREE_MAX_TAGS. Код взят из ветки 2.6.30</i>). Имейте ввиду, что использование в качестве тэга любого значения, отличного от 0 или 1 приведёт к порче памяти в нежелательном месте; вас предупредили.</p><p align="justify">Тэг может быть уалён вызовом функции radix_tree_tag_clear(); и снова, в качестве возврата функция отдаст указатель на данные, с который снели тэг. Функция radix_tree_tag_get() производит проверку, был ли данный тэг установлен на записи, идентифицируемой данным ключом; если ключ не найден, функция возвратит 0, -1 - если ключ найден, но записи не был присвоен данный тэг, и +1 - в противном случае. Сейчас эта функция закомментирована в исходном коде, т.к. она не используется кодом текущей версии ядра (<i>Опять же, если судить по коду версии 2.6.30, функция на месте, но в комментариях дано пояснение, что она используется исключительно кодом для внутреннего тестирования ядра</i>).</p><p align="justify">Для получения тэгов, писвоенных записям предусмотрены две функции:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>int radix_tree_tagged(struct radix_tree_root *tree, int tag);
unsigned int radix_tree_gang_lookup_tag(struct radix_tree_root *tree,
void **results,
unsigned long first_index,
unsigned int max_items,
int tag);</pre></span><p align="justify">radix_tree_tagged() возвращает ненулевое значение, если какой-либо из записей, хранимых в дереве присвоен данный тэг. Список записей с данным тэгом может быть получен с помощью функции radix_tree_gang_lookup_tag().</p><p align="justify">В заключение можно отметить интересную особенность API базисных деревьев: отсутствует функция, уничтожающая базисное дерево. Как очевидно подразумевается, базисное дерево будет существовать вечно. На самом деле, удаление всех записей из не корневых узлов базисного дерева освободит память, выделенную для хранения данных, а после этого дерево можно удалить обычным способом, освободив память, занятую корневым узлом.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-20199162454621593002011-10-16T08:26:00.000-07:002011-10-16T08:47:25.580-07:00Красно-чёрные деревья (Red black trees) в ядре Linux<img hspace=10 vspace=10 style="float: right" alt="" src="data:image/png;base64,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" /><p align="justify" >В отличие от предыдущей статьи данная статья описывает деревья в ядре Linux. Точнее говоря, описывается API, а не сами деревья, алгоритмы на них и правила формирования красно-чёрных деревьев. Если вас интересуют основы, то стоит обратить внимание на статью <a href="http://rflinux.blogspot.com/2011/10/red-black-trees.html">"Абстрактные типы данных - Красно-чёрные деревья (Red black trees)" от 14 октября 2011</a>. Здесь свойства красно-чёрных деревьев упомянуты лишь вкратце. Статья основана на материалах <a href="http://lwn.net/Articles/184495/">lwn.net</a></p><p align="justify">Наряду с базисными деревьями (radix trees) ядро содержит реализацию структуры данных, известной под названием "красно-чёрное дерево" (red-black tree). Красно-чёрные деревья (в ядре более известные, как "rbtrees") являются разновидностью полусбалансированных деревьев. Каждый узел дерева содержит некое значение и не более двух дочерних узлов; значение узла больше, чем значения любого из содержащихся в его левом поддереве или меньше любого из значений его правого поддерева. Поиск значения возможен с самого первого глубокого узла обходом слева направо.</p><p align="justify">Каждый узел красно-чёрного дерева может быть красного или чёрного цвета, корень дерева всегда чёрный. Набор правил, исходя из которых окрашиваются узлы и когда должна происходить перебалансировка несколько сложен. Данная статья не будет касаться этой темы (см. выше). Вместо описания механизмов работы красно-чёрных деревьев мы сконцентрируем внимание на том, как используются красно-чёрные деревья в ядре Linux.</p><p align="justify">Сложность правил формирования красно-чёрных деревьев окупается некоторыми преимуществами, в частности при том, что красно-чёрное дерево - это бинарное дерево, поиск значения осуществляется за логарифмическое время. Если дерево поддерживается в надлежащем состоянии, то самый длинный путь к листу никогда не будет превышать двукратной длины наикратчайшего пути - иными словами, дерево всегда почти сбалансировано. Но самое важное свойство красно-чёрных деревьев с точки зрения ядра это то, что операции вставки и удаления (1) быстрые и (2) имеют гарантированное время обработки. Вся работа разработчиков ядра по сокращению временных издержек при выполнении кода пошла бы насмарку, если бы какая-либо из используемых ядром структур данных отнимала много времени, скажем, на перебалансировку. При использовании красно-чёрных деревьев есть небольшие издержки, связанные с операцией поиска, т.к. дерево сбалансировано не идеально, однако операции вставки и удаления быстры и выполняются за гарантированно короткое время. Таким образом, красно-чёрные деревья находят своё применение там, где часто необходимо добавлять и удалять узлы.</p><p align="justify">В ядре множество мест, где используются красно-чёрные деревья. Планировщики ввода-вывода anticipatory (упреждающий), deadline (алгоритм крайнего срока) и CFQ (completely fair queuing - абсолютно честная очередь) используют красно-чёрные деревья для отслеживания запросов; драйвер пакетной записи CD/DVD использует красно-чёрные деревья для этих же целей. Код таймеров высокого разрешения использует красно-чёрное дерево для упорядочивания невыполненных запросов на таймеры. Файловая система ext3 отслеживает в красно-чёрных деревьях содержимое (записи) директорий. Также с помощью красно-чёрных деревьев отслеживаются диапазоны виртуальных адресов (VMAs), дескрипторы файлов, на которых применяется опрос вызовом epoll(), криптографические ключи и сетевые пакеты в планировщике "hierarchical token bucket" (классовая дисциплина очереди НТВ).</p><p align="justify">Использование красно-чёрных деревьев начинается с включения заголовка <linux/rbtree.h>. Это одна из самых хитрых в использовании структур данных ядра. При разработке структуры данных на языке С разработчику всегда приходится решать, каким образом включить в эту структуру некие данные произвольного типа и как сравнивать их между собой. Человек, который написал реализацию красно-чёрных деревьев в Linux (копирайт в коде принадлежит Андреа Арканджели (Andrea Arcangeli)) принял следующие решения:<ul><li>Структуры, которые являются частью красно-чёрного дерева (учёт которых ведётся с помощью дерева) должны иметь поле типа rb_node; для ссылок на объекты используются не указатели void*. Это общий принцип реализации структур данных ядра, так что едва ли это много кого удивит.</li><li>В коде красно-чёрных деревьев нет функции обратного вызова (колбэка) "сравнить два объекта". Вместо этого для использования дерева пользователь должен сам написать функции поиска и вставки, используя примитивные функции, предоставляемые интерфейсом реализации красно-чёрных деревьев. В итоге использование красно-чёрных деревьев требует немного больше работы, а сама структура не так непрозрачна, как больше пришлось бы по вкусу преподавателю компьютерных наук. Тем не менее результатом такого подхода является скорость и отсутствие кучи функций, поддерживающих инфтраструктуру там, где интенсивны операции поиска.</li></ul></p><p align="justify">Необходимо также помнить, что подобно многим структурам данных ядра красно-чёрные деревья (далее просто rbtree) не предоставляют механизмов блокировки сами по себе. Любой код, использующий rbtree должен обеспечивать собственный механизм взаимного исключения для поддержания дерева в целостном состоянии. Обычно, блокирока будет соответствовать схемам, используемым в других, уже существующих частях кода и поэтому нет необходимости изобретать некий свой механизм блокировок.</p><p align="justify">Корень красно-чёрного дерева имеет тип struct rb_root; пустое дерево может быть проинициализировано строкой:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>struct rb_root the_root = RB_ROOT;</pre></span><p align="justify">Предположим, у нас уже есть красно-чёрное дерево, ломящееся от всяких интересных данных. Обход дерева (без поиска) прямолинеен:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>struct rb_node *rb_first (struct rb_root *tree);
struct rb_node *rb_last (struct rb_root *tree);
struct rb_node *rb_next (struct rb_node *node);
struct rb_node *rb_prev (struct rb_node *node);</pre></span><p align="justify">Функция rb_first() возвратит указатель на первый узел дерева, а rb_last() - на последний. Передвижение вперёд и назад - простые операции, выполняемые вызовами функций rb_next() и rb_prev(). Во всех случаях возврат NULL означает, что узел не существует.</p><p align="justify">Т.к. структура rb_node внедряется в пользовательскую структуру, поиск нужного узла rb_node осуществляется просто при использовании нужного поля нашей пользовательской структуры. Вызов любой из упомянутых выше функций возратит указатель на внедрённое поле типа rb_node. Однако, это поле не будет содержать интересующих нас данных. Это та ситуация, когда нужен макрос container_of(), но у нас нет необходимости напрямую использовать container_of(). Вместо этого используйте rb_entry():</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>rb_entry(pointer, type, member);</pre></span><p align="justify">Здесь pointer - указатель на член rb_node в нашей структуре, type - тип нашей пользовательской структуры, а member - имя поля типа rb_node внутри контейнера (структуры, хранимой в дереве).</p><p align="justify">Поиск значения в дереве начинается с корня, затем циклически значение каждого узла сравнивается с искомым ключом и последующим переходом к более левой ветви, если необходимо. Т.о., код функции поиска в rbtree может выглядеть приблизительно так:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>struct my_stuff *my_rb_search (struct rb_root *root, int value)
{
struct rb_node *node = root->rb_node; /* top of the tree */
while (node) {
struct my_stuff *stuff = rb_entry (node, struct my_stuff, node);
if (stuff->coolness > value)
node = node->rb_left;
else if (stuff->coolness < value)
node = node->rb_right;
else
return stuff; /* Found it */
}
return NULL;
}</pre></span><p align="justify">Здесь мы ищем структуры типа struct my_stuff, сравнивая поле coolness с заданным значением. Для простоты мы используем целочисленные значения, но не всякое использование деревьев обязано быть таким простым. Если значение coolness корня больше, чем искомое значение, поиск должен быть продолжен в левой ветви дерева (если это значение вообще присутствует в дереве), так что мы переходим по указателю rb_left и начинаем сначала. Если искомое значение больше, чем значение из текущего узла, нам надо перейти на правую ветку. Наконец, либо функция найдёт точное совпадение, или достигнет "конца" дерева.</p><p align="justify">Код вставки немного сложнее. Он должен обойти дерево в поисках листа, где можно вставить новый узел. Как только такое место найдено, новый узел вставляется с цветом "красный", а дерево перебалансируется при необходимости. Код функции вставки может выглядеть так:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void my_rb_insert (struct rb_root *root, struct my_stuff *new)
{
struct rb_node **link = &root->rb_node, *parent;
int value = new->coolness;
/* Go to the bottom of the tree */
while (*link) {
parent = *link;
struct my_stuff *stuff = rb_entry (parent, struct my_stuff, parent);
if (stuff->coolness > value)
link = &(*link)->rb_left;
else
link = &(*link)->rb_right;
}
/* Put the new node there */
rb_link_node (new, parent, link);
rb_insert_color (new, root);
}</pre></span><p align="justify">В этом случае обход дерева более похож на поиск. Указатель link имеет вторую степень косвенности; в конце этот указатель используется, чтобы уведомить код rbtree, какая из ветвей (rb_left или rb_right) будет содержать новое значение. Код проходит по всему дереву до конца, указатель parent содержит адрес родителя для вставленного узла, а link указывает на соответствующее поле родительского узла. Затем вызывается функция:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void rb_link_node (struct rb_node *new_node,
struct rb_node *parent,
struct rb_node **link);</pre></span><p align="justify">Вызов этой функции встроит новый узел в дерево, как узел красного цвета. После этого вызова дерево может перестать соответствовать требованиям построения красно-чёрных деревьев и в таком случае необходима перебалансировка. Эта работа выполняется функцией:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void rb_insert_color (struct rb_node *new_node, struct rb_root *tree);</pre></span><p align="justify">На этом шаге дерево должно содержать новые данные и быть сбалансированным.</p><p align="justify">В примере выше делалось важное предположение: новое значение, добавляемое в дерево, не существовало в дереве ранее. Если это предположение оказывается неверным, дерево может быть испорчено. Если есть вероятность дубликатов в дереве, код должен тщательно проверить наличие таких дубликатов (как делается в случае поиска) и остановиться (без вставки узла) если найдено совпадение.</p><p align="justify">Удаление узла из дерево - очень простой шаг; просто вызовите функцию:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void rb_erase (struct rb_node *victim, struct rb_root *tree);</pre></span><p align="justify">После вызова данной йункции узел victim больше не будет являться частью дерева, которое может потребовать перебалансировки после операции удаления. Если один узел дерева должен быть заменён другим с тем же самым значением, необязательно последовательно проводить удаление и вставку. Вместо этого просто используйте функцию:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void rb_replace_node (struct rb_node *old,
struct rb_node *new,
struct rb_root *tree);</pre></span><p align="justify">Этот вызов быстро удалит из дерева старый узел, заменив его на new. Однако, если new имеет не такое же значение, как old, дерево будет испорчено.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-39437432388599604822011-10-14T11:23:00.000-07:002011-10-19T02:48:57.552-07:00Абстрактные типы данных - Красно-чёрные деревья (Red black trees)<a name="top"></a><table><tr><td><a href="#section0">Концепция</a></td></tr><tr><td><a href="#section1">Вставка снизу вверх</a></td></tr><tr><td><a href="#section2">Удаление снизу вверх</a></td></tr><tr><td><a href="#section3">Вставка сверху вниз</a></td></tr><tr><td><a href="#section4">Удаление сверху вниз</a></td></tr><tr><td><a href="#section5">Заключение</a></td></tr></table><br />
<h4>Вместо предисловия</h4>
<p align="justify">...от переводчика. Данная статья является переводом материалов с сайта Жюльена Валкера (Julienne Walker) <a href="http://eternallyconfuzzled.com">eternallyconfuzzled.com</a>. Там вы сможете найти множество других статей на английском, включая и оригинал этой статьи. Итак, почему я решил опубликовать здесь статью про красно-чёрные деревья - абстракный тип данных? Абстрактные типы данных, такие, как бинарные деревья, очереди, хэш-таблицы и проч. интенсивно используются в ядрах операционных систем. От успешности реализации этих структур в немалой степени зависит производительность ядра. Поэтому, я считаю, что знание таких структур и алгоритмов работы с ними не менее важно, чем знание устройства различных подсистем и железа. Для примера, те же самые красно-чёрные деревья используются в драйвере ext3, подсистеме управление памятью при упорядочивании диапазонов выделения виртуальных адресов, планировщике запросов ввода-вывода, драйверах некоторых физических устройств и даже планировщике процессов ядра Linux. Да и вообще, знание таких алгоритмов должно пойти на пользу не только тем, кто хочет лучше познакомиться с ядрами изнутри, но и любому программисту, который, быть может, никогда не имел дело с кодом ядра и занят исключительно юзерлэндом. Современные фреймворки вроде монструозного .NET с его рантаймом, который скрывает от пользователя (программиста) все тонкости таких структур, не заменят знания и для написания небольших по размерам результирующего кода, эффективных и быстрых приложений по прежнему необходимо понимание хотя бы базовых алгоритмов. Для лучшего понимания читателю стоит познакомиться с бинарными деревьями поиска прежде чем приступать к чтению данного материала. У автора данного руководства есть статья и об этих деревьях, но, к сожалению, объём статей достаточно велик, а я не всегда располагаю свободным временем в необходимой мере. Возможно, в будущем я переведу и опубликую здесь и их, но не могу обещать слишком многого. В целях наглядности я преобразовал оригинальные схемы в ASCII в рисунки. Данные картинок внедрены в HTML в виде Base64 строк. По идее, все современные браузеры должны без проблем отображать эти внедрённые строки, как графику. Код, приведённый в статье, не предназначен для работы внутри ядра. Это всего лишь пояснение на примере, если можно так выразиться. Более того, эта реализация деревьев вообще не имеет ничего общего с теми, что в ядре. Цель статьи, однако, дать представление об алгоритмах на красно-чёрных деревьев безотносительно к ядру. Заранее приношу свои извинения за возможные неточности. Перевод делался в тесных временных рамках и поэтому не застрахован от ошибок. Я буду вам весьма признателен, если со временем вы поможете мне улучшить статью, указав в комментариях на найденные вами ошибки, неточности и прочие недочёты. Ещё, на примере прошлых статей я обнаружил прискорбный факт. Люди оставляют свои вопросы или комментарии, но я замечаю их слишком поздно и даже не знаю, есть ли смысл отвечать на вопросы, заданные несколькими месяцами ранее. Я не очень часто захожу сюда, если быть честным до конца и потому прошу прощения у всех, чьи вопросы так и остались без ответов, либо ещё останутся в будущем. Однако, если вы, уважаемый читатель, найдёте в комментариях вопрос, на который сможете ответить и тем самым помочь кому-то, пожалуйста, сделайте это. Я буду только благодарен вам. Ну, вот вроде бы и всё. Приятного чтения и надеюсь, вы найдёте здесь что-то полезное для себя.<br /><br />Поехали.</p><p align="justify">Добро пожаловать обратно. Или, если это ваше первое знакомство с красно-чёрными деревьями, приготовьтесь хорошо провести время. Но для начала выясним, зачем нужна ещё одна статья по красно-чёрным деревьям? Любой, кто введёт соответствующий запрос в Google, получит в ответ тонны ссылок на различные ресурсы, включая учебники, общие описания, Java-апплеты, документы, библиотеки и даже презентации в Power Point. Хорошие новости. Но недостаточно хорошие. Большинство ресурсов, посвящённых данной теме ограничивается лишь описанием операции вставки. Прочие не описывают операцию удаления доступным образом, чтобы кто угодно смог понять, почему это работает, полагая, что перечисления различных ситуаций должно быть достаточно. Что ещё хуже, ни один ресурс не упоминает о том, что есть несколько различных реализаций красно-чёрных деревье.</p><p align="justify">Операция вставки для красно-чёрных деревьев проста до предела. Любой программист даже с половиной мозга и небольшим опытом использования бинарных деревьев поиска может разобраться с ней, не прилагая особых усилий. В противоположность этому, операция удаления по правде действительно может стать занозой в одном месте. Куда проще исследовать различные случаи самому, чем понять, почему используются некоторые из традиционных методов. Например, почему соседний узел должен быть чёрным? Это особый случай и таково требование, но, кажется, никто не знает, почему. После изучения этого случая я могу рассказать и показать, почему.</p><p align="justify">Когда речь заходит о красно-чёрных деревьях, только один источник принимается всеми: "Введение в алгоритмы" Кормена, Лейзерсона и Ривеста (“Introduction to Algorithms” by Cormen, Leiserson, and Rivest), книга, также известная под аббревиатурой CLR. И это на самом деле хороший источник, потому что в книге описывается не только вставка и удаление, но приводится достаточно псевдокода для обеих операций. Принимая во внимание, что это известный источник и его текст легко доступен, чуть ли не каждая реализация красно-чёрных деревьев, используемая на практике, является трансляцией той, версии, что изложена в CLR. К сожалению, подход, описанный в CLR сложен и неэффективен, весьма интенсивно использует родительские указатели. Однако красно-чёрные деревья не обязаны быть такими сложными.</p><p align="justify">Для меня изучение красно-чёрных деревьев было непростым. Имея лишь описание правил, я прорабатывал каждый случай, какой только мог придумать, пытаясь вывести алгоритм. Не имея копии CLR, я не мог увидеть, как предполагалось решать проблему. Это хорошо, потому что я нашёл разные решения и, т.о., я избавился от большинства сложностей оригинального алгоритма. Ранясь об острые углы алгоритмов, я приходил в отчаяние, пытаясь заставить их работать и теперь я делюсь с вами результатами своих усилий, дабы вам не пришлось идти той же тропой.</p><p align="justify">Эта статья описывает обе разновидности алгоритмов - нерекурсивные операции сверху-вниз и рекурсивные алгоритмы снизу-вверх как для вставки, так и для удаления. Все вариации проиллюстрированы кодом на С. Наиболее распространённая реализация основана на псевдокоде из CLR, однако я не буду её освещать, потому что это всего лишь реализация алгоритма с направлением снизу-вверх. Если вас интересует реализация красно-чёрного дерева с родительскими указателями, в вашем распоряжении либо CLR (см. выше), либо прекрасная <a href="http://adtinfo.org">онлайн-книга</a> Бена Пфаффа (Ben Pfaff). Если вас интересует нерекурсивная реализация дерева с использованием стека вместо рекурсии, опять же, упомянутая книга Бена Пфаффа описывает и её. Я не вижу смысла лишний раз воспроизводить здесь то, что где-то уже было хорошо описано.</p><a name="section0"></a><h4>Концепция <a href="#top">[наверх]</a></h4><p align="justify">По своей сути бинарные деревья поиска являются простой структурой данных с эффективными операциями поиска, вставки и удаления за <i>O(lg n)</i> итераций. Это в большой степени справедливое утверждение, если мы предполагаем, что входные данные не упорядочены. Однако по крайней мере в 3 наихудших случаях бинарные деревья поиска перестают быть структурами данных с логарифмической эффективностью, вырождаясь в знаменитый связный список. Два таких наихудших случая - это упорядоченные данные на входе, отсортированные либо в возрастающем, либо в убывающем порядке (третий из упомянутых случаев - the third is outside-in alternating order). При том, что бинарные деревья хранят данные в отсортированном виде, если на входе у нас также упорядоченные данные, это порождает проблему. Например, добавим в бинарное дерево поиска последовательно следующие значения 0, 1, 2, 3, 4. Т.е. каждое новое значение больше предыдущего, оно будет добавлено в правую ветку как дочерний узел к узлу предыдущего значения:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Такое бинарное дерево поиска не очень-то хорошее. Производительность падает с <i>O(lg n)</i> до <i>O(n)</i>, потому что теперь дерево фактически является линейной структурой данных. Было бы лучше, если бы каждый узел имел два дочерних узла вместо одного. Т.о., мы бы сполна воспользовались преимуществом бинарной природы дерева поиска:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Много клеток мозга пало в бою за идею, как гарантировать такую оптимальную структуру, которая называется сбалансированным бинарным деревом поиска. К сожалению, поддержание дерева в таком идеально сбалансированном состоянии требует массы усилий, так что невозможно гарантировать постоянство идеальной сбалансированности в любое время. Тем не менее, в наших силах подойти достаточно близко к тому, чтобы гарантировать логарифмическую эффективность дерева. Многие из таких "приближающих" стратегий нашли широкое применение. Два предшественника этих оптимизирующих схем - AVL-деревья и, собственно, красно-чёрные деревья. Здесь мы коснёмся только красно-чёрных деревьев, т.к. по AVL-деревьям есть отдельная статья.</p><p align="justify">Изначально красно-чёрные деревья были абстракцией абстракции, которая стала самостоятельной структурой данных. Рудольф Байер (Rudolf Bayer) предложил абстракцию, которую он назвал симметричным бинарным B-деревом. Мысль состояла в моделировании B-дерева 4-го порядка (каждый узел может иметь 4 ссылки в то время, как бинарное дерево допускает лишь две ссылки на узел). Принимая во внимание, что все пути, ведущие от корня к некоему листу дерева, содержат одно и то же число узлов, все листья в B-дереве находятся на одном уровне. Это идеально сбалансированное дерево, однако не бинарное дерево поиска, как требовалось.</p><p align="justify">Основная идея симметричных бинарных B-деревьев состоит в том, что узел может иметь горизонтальные или вертикальные связи. Т.о., бинарное дерево поиска моделирует структуру узлов B-дерева. Вертикальные связи отделяют друг от друга два разных узла, а горизонтальные - узлы, которые логически рассматриваются, как один узел B-дерева. Ниже приведено сравнение эквивалентных B-деревьев и симметричных бинарных B-деревьев (* = неизвестный узел). Обратите внимание, как структура узлов B-дерева подменяется использованием нескольких узлов бинарного дерева:<div align="center"><img alt="" src="data:image/gif;base64,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" /></div></p><p align="justify">Алгоритмы на B-деревьях по меньшей мере запутанны, вероятно потому, что деревья растут вверх, а не вниз, что доставляет проблем программистам. Как итог, алгоритмы на B-деревьях не очень гладко преобразуются в алгоритмы абстрактных симметричных бинарных B-деревьев и они довольно сложны. Несмотря на то, что идея в целом была хороша, симметричным бинарным B-деревьям чего-то не хватало.</p><p align="justify">Позже Робертом Седжвиком (Robert Sedgewick) и Леонидасом Гибасом (Leonidas Guibas) была предложена мнемоническая абстракция, которая облегчала понимание симметричных бинарных B-деревьев (кроме того, название тоже сократилось!). Вводя новый атрибут узла - цвет, вы можете легко дифференцировать вертикальные и горизонтальные связи. В рамках этой абстракции узлы, являющиеся частью одного логического узла B-дерева (горизонтальная связь) имеют красный цвет, в то время, как узлы, отделяющие друг от друга логические узлы B-дерева (вертикальные связи) имеют чёрный цвет. Так на свет появились красно-чёрные деревья. Ниже приводятся деревья из 2, 3 и 4 узлов, представленные в виде красно-чёрной абстрактной модели:<div align="center">
<img alt="" src="data:image/gif;base64,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" /></div></p><p align="justify">Это даёт нам некоторые простые правила для сохранения сбалансированности. Во-первых, красный узел не может иметь дочерний узел также красного цвета, потому что это не удовлетворяет правилам абстракции. Если узел имеет красный цвет, он является частью логического узла, и единственно возможным цветом следующего узла является чёрный, потому что связи красного узла являются частью связей узла B-дерева со следующим узлом B-дерева. Нарушение этого правила называется красным нарушением.</p><p align="justify">Далее, т.к. в B-дереве путь от вершины до листа состоит из одного и того же числа узлов, все листья находятся на одном уровне, число чёрных узлов в любом из путей красно-чёрного дерева должно быть одинаковым. Т.к. материнский чёрный узел и его красные дочерние узлы являются частью одного и того же логического узла, красные узлы в пути не учитываются. Это называется чёрной высотой красно-чёрного дерева. Нарушение этого правила называется чёрным нарушением.</p><p align="justify">Наконец, вершиной красно-чёрного дерева должен быть узел чёрного цвета. Это не лишено смысла, т.к. только чёрный узел может иметь красных детей, а вершина не имеет родительского узла. Однако, это правило применимо к абстракции B-дерева. На деле вершина может быть либо красной, либо чёрной и это никак не влияет на эффективность красно-чёрного дерева.</p><p align="justify">Все алгоритмы, обеспечивающие балансировку красно-чёрного дерева, не должны нарушать ни одного из этих правил. Если все правила соблюдаются, то дерево является действительно красно-чёрным и его высота не может быть короче <i>lg (n + 1)</i>, но также не выше <i>2lg (n + 1)</i>. Эти величины логарифмические, так что красно-чёрное дерево гарантирует аппроксимацию наилучшего случая бинарного дерева поиска.</p><p align="justify">Красно-чёрные деревья получили широкое распространение и оригинальная абстракция была заменена структурой данных, которая просто избегает нарушения двух базовых правил: красный узел не может иметь красных потомков и каждый путь состоит из одного и того же числа чёрных узлов. Этого достаточно для выполнения требований, накладываемых красно-чёрными деревьями для обеспечения производительности. Заметьте, что вершина при таких условиях не обязана быть чёрной, но это упрощает алгоритмы, потому что вы можете избежать особого случая красного нарушения на вершине.</p><p align="justify">Прежде чем мы приступим, нам понадобится основа и небольшой скелет для кода. Для начала, структуры узла и дерева. Каким образом вы введёте цвет, зависит от вас. Общее решение - поле перечисляемого типа, принимающее значение одной из констант - одна для чёрного цвета, вторая - для красного. Однако, я считаю, что проще использовать флаг. Если он установлен, узел красный, если не установлен - чёрный. Естественно, можно и инвертировать значения, но в данной статье взведённый флаг будет обозначать красный цвет:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 struct node {
2 int red;
3 int data;
4 struct node *link [2];
5 };
6
7 struct tree {
8 struct node *root;
9 };</pre></span><p align="justify">Уверен, что если вы не читали прежде прочие мои статьи о деревьях, то массив ссылок будет вас путать. Вместо использования двух указателей - левого и правого, как это делается обычно, я предпочитаю использовать массив, где элемент с индексом 0 содержит левый указатель, а элемент с индексом 1 - правый указатель. Это облегчает задачу объединения симметричных случаев и сокращает код, гарантируя корректность. Это моя личная идиома и насколько мне известно, только я использую её для объединения симметричных случаев. Чтобы свести шок к минимуму, ниже показана разница между "классической" идиомой и моей собственной:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 /* Classic binary search tree insertion */
2 struct cbt_node *cbt_insert (struct cbt_node *root, int data)
3 {
4 if (root == NULL)
5 root = make_node (data);
6 else if (data < root->data)
7 root->left = cbt_insert (root->left, data);
8 else
9 root->right = cbt_insert (root->right, data);
10
11 return root;
12 }
13
14 /* Julienne Walker's binary search tree insertion */
15 struct node *insert (struct node *root, int data)
16 {
17 if (root == NULL)
18 root = make_node (data);
19 else {
20 int dir = root->data < data;
21 root->link [dir] = insert (root->link [dir], data);
22 }
23
24 return root;
25 }</pre></span><p align="justify">Пока что здесь вроде не видно экономии кода, но если вы добавите 50 строк кода перебалансировки для обоих случаев - левого и правого направления, то легко увидеть, что классическая вставка требует дополнительных 100 строк кода, однако мой код для операции вставки умещается всего в 50 строк, потому что оба симметричных случая объединены в один. В реализации алгоритма результатом этого объединения является использование флага dir, который указывает направление, по которому мы работаем, а !dir - противоположное направление. Когда вы поймёте эту идиому, чтение кода, использующего его перестанет быть трудным.</p><p align="justify">Вернёмся обратно к нашему каркасу. Чтобы не захламлять код дополнительными проверками, му будем использовать небольшую вспомогательную функцию, которая будет определять, что цвет узла красный. Чтобы облегчить жизнь, мы будем исходить из предположения, что лист чёрный, потому что наши листья - указатели на NULL, а попытки исправить красное нарушение с NULL-указателями способны свести с ума :-)</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 int is_red (struct node *root)
2 {
3 return root != NULL && root->red == 1;
4 }</pre></span><p align="justify">Наконец, вращение. Любая схема балансировки дерева использует вращения, чтобы изменить структуру дерева без нарушения правил, действующих для бинарных деревьев поиска. Также в этом случае было бы логично использовать перекраску узлов, когда они вращаются. Одиночный поворот в красно-чёрном дереве просто поворачивает узлы, как обычно, затем "окрашивает" старую вершину в красный цвет, а новую - в чёрный. Двойной поворот это просто два одиночных поворота. Этого достаточно для операции вставки, но с удалением всё сложнее (какой шок). Несмотря на это, перекраска может быть полезной нам и при вращениях в алгоритме операции удаления. Мы добавим перекраску в функцию одинарного поворота, а функция двойного поворота будет использовать перекраску не явно:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 struct node *rot_single (struct node *root, int dir)
2 {
3 struct node *save = root->link [!dir];
4
5 root->link [!dir] = save->link [dir];
6 save->link [dir] = root;
7
8 root->red = 1;
9 save->red = 0;
10
11 return save;
12 }
13
14 struct node *rot_double (struct node *root, int dir)
15 {
16 root->link [!dir] = rot_single (root->link [!dir], !dir);
17 return rot_single (root, dir);
18 }</pre></span><p align="justify">Не волнуйтесь, мы не будем заниматься вставкой прямо сейчас. Сбалансированные деревья нетривиальная структура данных, а красно-чёрные деревья на этом фоне выглядят исключительно хитро для правильного понимания, потому что даже небольшое изменение в одной части дерева может привести к нарушениям в дальних его частях. Т.о., есть смысл ввести небольшую функцию для верификации, которая будет проверять, не было ли нарушений. Зачем? Может так статься, что алгоритм работает на небольших деревьях, но ломается на крупных и вы не знаете, почему. Используя такую функцию, мы можем быть уверены, что алгоритм работает, если мы нагрузим его достаточным объёмом данных в достаточно большом дереве (слишком большом, чтобы тестировать его вручную). Т.к. эта функция будет использоваться только для отладки, мы можем применить рекурсию и допустить, чтобы наш тестер был медленным:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1 int rb_assert (struct node *root)
2 {
3 int lh, rh;
4
5 if (root == NULL)
6 return 1;
7 else {
8 struct node *ln = root->link [0];
9 struct node *rn = root->link [1];
10
11 /* Consecutive red links */
12 if (is_red (root)) {
13 if (is_red (ln) || is_red (rn)) {
14 puts ("Red violation");
15 return 0;
16 }
17 }
18
19 lh = rb_assert (ln);
20 rh = rb_assert (rn);
21
22 /* Invalid binary search tree */
23 if ((ln != NULL && ln->data >= root->data)
24 || (rn != NULL && rn->data <= root->data))
25 {
26 puts ("Binary tree violation");
27 return 0;
28 }
29
30 /* Black height mismatch */
31 if (lh != 0 && rh != 0 && lh != rh) {
32 puts ("Black violation");
33 return 0;
34 }
35
36 /* Only count black links */
37 if (lh != 0 && rh != 0)
38 return is_red (root) ? lh : lh + 1;
39 else
40 return 0;
41 }
42 }</pre></span>
<p align="justify">Этот алгоритм относительно прост. Он совершает обход каждого узла в дереве и проводит различные проверки на самом узле и его потомках. Первое - проверка, имеет ли красный узел красные дочерние узлы. Второе - проверка, что дерево является валидным двоичным деревом поиска. И последнее - подсчёт чёрных узлов в пути, а также проверка на предмет того, что все пути имеют такую же длину - чёрную высоту. В случае, если rb_assert() возвращает 0, наше дерево не является корректно построенным красно-чёрным деревом. В противном случае функция вернёт чёрную высоту всего дерева. Для удобства будет выведено сообщение, поясняющее, какое именно нарушение произошло. :-)</p><p align="justify">Теперь мы готовы перейти к вставке! Засучите рукава и приготовьтесь повозиться.</p><a name="section1"></a><h4>Вставка снизу вверх <a href="#top">[наверх]</a></h4><p align="justify">Когда нам нужно добавить элемент в красно-чёрное дерево, мы сразу сталкиваемся с необходимостью принять решение. Какого цвета должен быть новый элемент - красного или чёрного? Какой алгоритм мы будем писать, зависит от этого решения, потому что от этого зависит, будут ли внесены нарушения дерева. Если новый узел чёрный, его вставка в дерево приведёт к чёрному нарушению. Оставшаяся часть алгоритма должна быть сконцентрирована на том, чтобы устранить это чёрное нарушение, не внося красных нарушений. С другой стороны, если новый узел будет красным, есть вероятность возникновения красного нарушения и остальная часть алгоритма должна быть направлена на устранение красного нарушения без внесения чёрных нарушений.</p><p align="justify">Но подождите! Если новый узел красный и он будет добавлен, как дочерний узел чёрного узла, мы избежим нарушений вообще в то время, как при вставке чёрного узла в качестве дочернего узла по отношению к чёрному узлу нарушение будет происходить всегда. Т.о., логически наш выбор - окрашивать новый узел в красный цвет, потому что это даст нам возможность не нарушать правила формирования дерева. Нельзя сказать того же о вставке чёрного узла. Красные нарушения более локализованы и поэтому из проще устранять. По этой причине мы будет окрашивать новые узлы в красный цвет и исправлять красные нарушения во время операции вставки. Лень побеждает! Как насчёт вспомогательной функции, которая будет возвращать новый красный узел?</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 struct node *make_node (int data)
2 {
3 struct node *rn = malloc (sizeof *rn);
4
5 if (rn != NULL) {
6 rn->data = data;
7 rn->red = 1; /* 1 is red, 0 is black */
8 rn->link [0] = NULL;
9 rn->link [1] = NULL;
10 }
11
12 return rn;
13 }</pre></span><p align="justify">Ах, наконец и вставка. Добавление узла. Вместо того, чтобы красиво рассказывать о красно-чёрных деревьях мы можем уже поиграть с чем-то реальным. Хорошо, давайте вставим новый узел в базовое бинарное дерево поиска - это первое дело в нашей повестке. Затем мы можем пройти обратно вверх и перебалансировать дерево, если у нас возникло красное нарушение. Для простоты в коде вставки мы будем использовать рекурсию с запретом дубликатов. Не забудьте отметить последнюю часть кода, которая красит вершину в чёрный цвет. Это важный момент, потому что он гарантирует ненарушение последнего правила красно-чёрного дерева и позволяет избежать необходимости иметь дело с красными нарушениями на вершине. Код должен быть ясен. Если нет, вам стоит познакомиться с бинарными деревьями поиска.</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 struct node *insert_r (struct node *root, int data)
2 {
3 if (root == NULL)
4 root = make_node (data);
5 else if (data != root->data) {
6 int dir = root->data < data;
7
8 root->link [dir] = insert_r (root->link [dir], data);
9
10 /* Hey, let's rebalance here! */
11 }
12
13 return root;
14 }
15
16 int insert (struct tree *tree, int data)
17 {
18 tree->root = insert_r (tree->root, data);
19 tree->root->red = 0;
20 return 1;
21 }</pre></span><p align="justify">Этот код настолько прост, насколько это вообще возможно в случае бинарных деревьев, но мы знаем, что вставка может потребовать перебалансировки дерева снизу вверх, естественно, после рекурсивного вызова должен идти код перебалансировки. Помните те занятия по программированию, где надо было вывести введённую последовательность чисел в обратном порядке, используя рекурсию? Тот же принцип применим и здесь. Мы рекурсивно вызываем функцию, вниз, вниз, вниз (вниз! сесть! встать!), а затем вставляем новый узел, присоединяя его к первому листу, до кторого добираемся (потому что второй лист, до которого мы доберёмся, вероятно вызовет ошибку сегментации (SEGFAULT)). Затем рекурсия раскучивается вспять и мы можем извлечь из этого пользу для себя.</p><p align="justify">Итак, как нам провести проверку на красное нарушение? Очень просто: если какой-то из узлов на пути красный, проверим есть ли у него красные дочерние узлы - первый и второй. Если это так, исправляем. Обратите внимание, что только один дочерний узел будет красным. Если иначе, что-то ещё не так с нашим деревом, потому что таким образом красное нарушение будет присутствовать всегда и дерево не будет правильным красно-чёрным деревом. Однако, мы предположим, что дерево построено правильно, потому что так проще.</p><p align="justify">Хорошо, как нам <i>устранить</i> красное нарушение? Это тоже просто! Здесь возможны лишь три случая и все они просты. В первом случае мы проверяем оба дочерних узла (запомните, мы не проверяем новые узлы). Если оба дочерних узла красные, родительский узел должен быть чёрным, так что нам достаточно просто перевернуть цвета. Родительский узел становится красным, а дочерние узлы - чёрными:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Это решение нашей непосредственной проблемы. Но если меняется цвет родительского узла, возникает риск нарушений далее вверх по дереву по восходящей. Это важный момент, когда дочерние узлы и родительский узел данного узла не меняют цвета. Т.о., красное нарушение не будет распространяться дальше. Если узел 2 на диаграмме выше остался чёрным, мы могли бы закончить. Но ввиду того, что он изменил цвет с чёрного на красный, возможно, что родительский узел также мог быть красным и это уже другое нарушение. Поэтому, в данном случае мы поднимаемся выше и осматриваемся вокруг в поисках возможных проблем.</p><p align="justify">Второй случай, если соседний узел (3 в нашем случае) данного узла (1 в нашем случае) не является красным. Если это так, то переворачивание цветов не сработает, т.к. увеличилась бы чёрная высота нисходящего пути справа от 2. Здесь нам нужно проделать больше работы и это уже требует вращения. Во втором случае левый дочерний узел 1 вызвал бы нарушение, поэтому мы производим один поворот вокруг 2 направо, делаем узел 2 красным, 1 - чёрным:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Это устраняет красное нарушение, корень поддерева не меняет цвет, так что мы можем быть уверены, что красное нарушение не будет распространяться вверх. Но всё даже лучше! Вращение не меняет чёрную высоту поддерева, дерево теперь сбалансировано и мы готовы со вращением! Но почему не изменилась чёрная высота? Заметьте, что на диаграмме до изменения, чёрная высота левого поддерева узла 2 равна 1 (включая сам узел 2), а чёрная высота правого поддерева узла 2 равна 2. Теперь посмотрим на диаграмму, где изображено дерево после изменения. Высота левого поддерева по прежнему 1, а правого - 2! Не забудьте, что красные узлы не учитываются при подсчёте чёрной высоты.</p><p align="justify">Да, хорошая догадка, это дерево не является валидным красно-чёрным деревом. Почему? Потому что значения чёрной высоты в двух поддеревьях не одинаковы. То, с чем мы здесь столкнулись - чёрное нарушение! Хорошая новость - это никогда не произойдёт. Данный пример был призван лишь продемонстрировать, как работает поворот дерева, не рисуя большого дерева, но именно в точности такое дерево никогда не будет существовать в реальных условиях. Почему? Потому что было чёрное нарушение до вставки узла 0! На самом деле, первое вращение красно-чёрного дерева, состоящего из убывающей последовательности чисел произошло бы только после седьмого числа. Давайте посмотрим на этот случай после вставки 7:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">На данном этапе будет проделано переворачивание цвета, потому что 5, 4 и 6 попадают под упомянутый выше первый случай. Переворачивание цветов может вызвать красное нарушение выше, поэтому мы поднимаемся до узла 3 и видим, что на узле 3 у нас действительно красное нарушение. Теперь применим случай с одиночным поворотом. Посмотрим, что произошло с нашим деревом перед вращением:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Самое важное, что стоит сейчас отметить, это то, что НЕТ чёрного нарушения! Каждый путь имеет по два чёрных узла и единственное нарушение - красное нарушение на узле 3. Когда мы делаем поворот вокруг узла 1, узел 3 становится новым чёрным корнем, а узел 1 становится красным. Дерево выглядит, как показано ниже. Заметьте, нет красных нарушений и все чёрные высоты одинаковы. Так работает случай с одним поворотом:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Теперь довольно легко понять, почему функция rot_single() устанавливает цвет именно так, как она это делает. Наконец случай, когда вместо левого красного дочернего узла красным является правый дочерний узел. Если правый дочерний узел красный, то одинарный поворот здесь не поможет. Это тот случай, когда нужен двойной поворот:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Стойте, стойте, стойте! Средний шаг верен, так? Так почему бы нам просто не сделать единичный поворот? Это ещё один пример, который не будет существовать в реальных условиях, как показано. Заметьте, что первый поворот увеличивает чёрную высоту поддерева левого по отношению к узлу 3. Это могло привести к чёрному нарушению, так что мы продолжаем двойное вращение, чтобы избежать этого. Попробуйте смоделировать такое правильно построенное красно-чёрное дерево, где этот случай имел бы место и убедитесь, что вращение работает так, как мы проделали выше.</p><p align="justify">Мы рассмотрели три случая, а теперь дополним наш код, чтобы он выявлял описанные случаи и мог исправлять подобные ситуации. Запомните, что новые узлы никогда не перебалансируют дерево; алгоритм просто возвращает новый узел, когда достигается лист и листья перебалансируются до уровня верхних узлов. Мы убеждаемся, что если имеет место нарушение, то оно локализовано в поддереве. Т.о., мы можем провести вращения не устанавливая флаг статуса, который предписывает алгоритму продолжать вращения далее по восходящей. На самом деле флаг статуса не нужен вовсе! Возможно переворачивать цвета на обратном пути вверх по дереву даже если может быть сделан одинарный или двойной поворот:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 struct node *insert_r (struct node *root, int data)
2 {
3 if (root == NULL)
4 root = make_node (data);
5 else if (data != root->data) {
6 int dir = root->data < data;
7
8 root->link [dir] = insert_r (root->link [dir], data);
9
10 if (is_red (root->link [dir])) {
11 if (is_red (root->link [!dir])) {
12 /* Case 1 */
13 root->red = 1;
14 root->link [0]->red = 0;
15 root->link [1]->red = 0;
16 }
17 else {
18 /* Cases 2 & 3 */
19 if (is_red (root->link [dir]->link [dir]))
20 root = rot_single (root, !dir );
21 else if (is_red (root->link [dir]->link [!dir]))
22 root = rot_double (root, !dir);
23 }
24 }
25 }
26
27 return root;
28 }
29
30 int insert (struct tree *tree, int data)
31 {
32 tree->root = insert_r (tree->root, data);
33 tree->root->red = 0;
34 return 1;
35 }</pre></span><p align="justify">Просто, элегантно, это и есть вставка снизу вверх в красно-чёрном дереве! Я советую вам пройтись по примеру, который покрывает все описанные случаи, чтобы убедиться, что это работает. Конечно, у вас есть вспомогательная функция для тестирования, но ведь вы не знаете, вдруг я просто дурачу ваc? Вы узнаете об этом, когда поэкспериментируете на приблизительно 30 случайных числах и оттрассируете вашу программу!</p><p align="justify">Вставка - это на самом деле самая простая операция на красно-чёрных деревьях. Красные нарушения легко обнаруживаются и устраняются, не вызывая проблем. Проблемы наступают, когда вы попытаетесь удалить узел из красно-чёрного дерева. Следующий раздел посвещён описанию операции удаления снизу вверх. Приготовьтесь!</p><a name="section2"></a><h4>Удаление снизу вверх <a href="#top">[наверх]</a></h4><p align="justify">Удаление узла из красно-чёрного дерева может послужить причиной сильной боли ниже спины. Это и моё официальное мнение. Я ненавижу эту операцию и научу вас ненавидеть её. В особенности, алгоритм удаления снизу вверх. В особенности, первый способ, который я собираюсь описать :-)</p><p align="justify">При вставке у нас была возможность выбирать цвет нового узла, чтобы сделать жизнь проще. Мы могли спровоцировать красное нарушение и тут же исправить его. Красные нарушения легко исправить и мы пользовались этим обстоятельством себе на пользу, чтобы создать элегантный рекурсивный алгоритм. Когда вы удаляете узел, у вас нет выбора и вы не можете манипулировать цветами. Если цвет узла красный - превосходно! Красные узлы удаляются без каких-либо нарушений. Почему? Ну, по крайней мере, я не вижу способа внести красное нарушение удалением красного узла из верно построенного дерева, но вы можете попытаться найти такой способ. Т.к. красные узлы не участвуют в формировании чёрной высоты, удаление красного узла не влияет на чёрную высоту. Следовательно, удаление красного узла не нарушает правила формирования красно-чёрного дерева.</p><p align="justify">Если узел чёрный, у нас проблема. Удаление чёрного узла наверняка спровоцирует чёрное нарушение, равно как и вставка чёрного узла. Это то обстоятельство, которого мы и пытаемся избежать при вставке, кроме того, весьма вероятно, что это послужит причиной и красного нарушения! Хорошо, давайте сначала удалим узел, а потом подумаем, как устранить нарушения. Ниже показан простой алгоритм рекурсивного удаления узла из чёрно-красного дерева. Если у узла, которой мы собираемся удалить меньше двух дочерних узлов, мы просто заменяем этот узел тем его потомком, который не равен NULL, или просто NULL-указателем, если у этого узла нет дочерних узлов.</p><p align="justify">Достаточно удобно, что здесь мы можем обойтись без перебалансировки. Если узел, который мы собираемся удалить, чёрный и имеет красный дочерний узел, мы можем окрасить потомка в чёрный цвет, тем самым не вызывая нарушений, потому что мы свели случай удаления чёрного узла к удалению красного узла, которое гарантирует отсутствие нарушений. Пусть это будет случай под номером 0.</p><p align="justify">Если у узла два потомка, мы находим такой узел, значение которого предшествуют значению данного узла - младший узел, (<i>т.е., для узла 3 на диаграмме ниже таким узлом будет 2</i> - перев.) и копируем его данные в наш узел. Затем мы рекурсивно удаляем младший узел, т.к. у него гарантированно по меньшей мере будет один дочерний узел. В целом, наш алгоритм выглядит довольно неплохо, даже с добавлением кода для перебалансировки (простая перекраска после удаления, флаг статуса и загадочная функция remove_balance ()). Заметьте, что корень окрашивается в чёрный цвет под конец, если дерево не пустое:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 struct node *remove_r (struct node *root, int data, int *done)
2 {
3 if (root == NULL)
4 *done = 1;
5 else {
6 int dir;
7
8 if (root->data == data) {
9 if (root->link [0] == NULL || root->link [1] == NULL) {
10 struct node *save =
11 root->link [root->link [0] == NULL];
12
13 /* Case 0 */
14 if (is_red (root))
15 *done = 1;
16 else if (is_red (save)) {
17 save->red = 0;
18 *done = 1;
19 }
20
21 free (root);
22
23 return save;
24 }
25 else {
26 struct node *heir = root->link [0];
27
28 while (heir->link [1] != NULL)
29 heir = heir->link [1];
30
31 root->data = heir->data;
32 data = heir->data;
33 }
34 }
35
36 dir = root->data < data;
37 root->link [dir] = remove_r (root->link [dir], data, done);
38
39 if (!*done)
40 root = remove_balance (root, dir, done);
41 }
42
43 return root;
44 }
45
46 int remove (struct tree *tree, int data)
47 {
48 int done = 0;
49
50 tree->root = remove_r (tree->root, data, &done);
51 if (tree->root != NULL)
52 tree->root->red = 0;
53
54 return 1;
55 }</pre></span><p align="justify">Здесь нам действительно необходим флаг статуса, чтобы уведомить алгоритм, когда остановить перебалансировку. Если remove_balance() вызывается каждый раз при проходе вверх по дереву, будет довольно некрасиво. Т.к. эта функция просто удаляет внешние узлы (не имеющие двух дочерних), нам достаточно лишь установить флаг после удаления узла (если узел красный или применим случай 0) и внутри remove_balance().</p><p align="justify">Ясно, что реальная работа выполняется функцией remove_balance(), но что именно делает эта вспомогательная функция? Конечно же, она обрабатывает все те случаи, возникновение которых может стать результатом удаление чёрного узла! И эта функция почти такая же по объёму кода, как remove_r(). :-( Давайте сначала рассмотрим простые случаи. Если мы удаляем узел и его сосед - чёрный, а его дочерние узлы также оба чёрные, мы имеем дело с простым случаем, который распространяется (на схеме * = удалённое место):</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">В этом случае мы можем просто перекрасить узел 1 (соседний узел) в красный цвет без внесения красного нарушения, но всё ещё существует вероятность чёрного нарушения, потому что мы уменьшили чёрную высоту левого поддерева узла 3. Это приводит к тому, что оба поддерева узла 3 будут иметь одну и ту же чёрную высоту, но если узел 3 сам является частью поддерева, то с точки зрения родительского узла возникнет чёрное нарушение, потому что чёрная высота соседнего поддерева не уменьшилась. Однако, если родительский узел (3 в нашем случае) изначально был красным, мы можем перекрасить соседний узел в красный цвет, родителя - в чёрный и дело сделано, потому что теперь чёрные высоты сбалансированы на всём восходящем по дереву пути. Для этого мы можем взвести флаг:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Теперь разберёмся с более сложными случаями, когда соседний узел чёрный, а какой-либо из его дочерних узлов или они оба - красные. Вообще, здесь у нас два случая. Если левый потомок соседнего узла красный, всё, что нам нужно сделать, это одиночный поворот. Однако, родительский узел может быть или красным, или чёрным, так что, нам нужно сохранить его цвет и позже восстановить его, потому что при вращении старые цвета старого и нового родительского узла во внимание не принимаются. После поворота новый родительский узел перекрашивается в цвет старого родительского узла, а его дочерние узлы становятся чёрными (на схеме E = какой-либо из двух цветов):</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Это восстанавливает чёрную высоту поддерева и флаг статуса может быть установлен, сигнализируя о том, что перебалансировка больше не требуется. То же происходит, если правый дочерний узел соседнего узла красный, за исключением того, что в таком случае мы используем двойной поворот, вместо одинарного. В итоге, цвета становятся идентичными. Помните, что левый дочерний узел соседа в данном случае должен быть чёрным. Если он красный, то применим предыдущий случай:</p><div align="center"><img alt="" src="data:image/gif;base64,R0lGODlhFAF3APcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///ywAAAAAFAF3AAAI/wAfCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDiuwIoKTJkShTqlzJsuUDkwhiyjzpsqbNmzhblpTJs2fJnECDCh3qEEDPoz6JKl3KtKZRpFBjAmhKtarVjE+jRp16tavXrwazat0KtqzZpmLHkj3Ltu3NtGqRcnVLt65IuHHl2nVq8ufeoXjzJv2bEqZPv4TfQu2bd27ijzvHIn68MrDYwIMpkxSMwLHmkZh5hhb9eeNovaVBxz0tNTVG1qhdQ9ZqeLXsirAX3/bIGrbn3Q1zrwWOdbVt4hCF60ZeXPJx5gyVD4dOEe/l59QTSl+efaLl2pK7K//8Hlntb/FFOXNHX9CyaOzs06s/ep5978bxI26fmT+scfj9LbRfffHdB2CACE0lHYEFmicYgwgKNJdwk0U4Wm4QRvgSQeVtpaCGHG7FWYYIngeeVJOR2J17I4KYoIB9mejiS3LFeCCIKkY3436kzdieRjlSxyN/Pg4UpIA+DnmkeEsiueN8rdkX44skRTclcwuiF+OWjjUZXIIVvhRmah2axySXaEoI0m9jtuelWVyVWeOZaNZ5F4fJuebZiZ359aZddQb6p45q6lcagVv+CJ2ggt7p5aBVvQnpWYwyGlKbRVE26KRgVVrpmq8lBimnX3lqKWSh/sUpqVeZ+mmVWEX/eiWVQN7m6qkcrcoUrniaZqulnvJWK1Gu9pqrnqcGe+ywQt0aJ6iHNqqgsszGGpSzNKEa1qyUfmrqstW+hS2m1ooZaLfAUluuk+zidtG4fd1VbKfeqpvqePGOt65+8OYL2a30psvrvtoRSnBR/WZLErYBn2vvwT9CyOqSCXMLJLylCtworPdqB3ByGp+ba8Je1TswxBJNnPLGD1tUMbnEvgyty+A+BKy5G19ccckya0tzuAiHLPJrL8MMGMmX/owyjELbiVXRrIKGMWjVXWvyydUNjbPTPDurEsXNXp2zyw6fhLWs836dZ9hNc6mz2GmWvPXZl2ZotE5tJ/p0sK+2/8ohy3yxebdLcMdNdM4tL1Wf3sYSbrFSUK/6bd+RVp1kz3sX/viuSuOIeebSjo3Wu0XuPHKa8dLd7NL59cub1pQrDrSG4675OeezW/jxv6aP7muRc2vN+9S+1+zitMLXXbu4/hrp8/FuNv81w4FzHbXzHku/2/Wgxz79tzP/DUAC5JevsJ5Upc2S10kbOX758MM/eF3cn+50/dnvLqyE8fcfP/6AyZj2ABg96qHqff5LYAIImBMGru97xFuYAie4QM04UCcoKRpkKEjBC2LwMQTUIEk4yEEPqk1T8jJc8NyWKxJ2sCuMC8uhlJeo1KkuZS4sYfpEZ0Kb1M9ONhTdu/9y+ELFgW9DZBpeDc12Q/0okCAk7GFwACZFH4JJe/hCHRObWJQnwu8BOowZ+2wlvm2RqGw/SRwOSQjGMFptebKJk90kBsQtCrE6LmxjEYHSO2St7YpuC+IdvZPHKL4RaegzFCCXmEYudpGDetwjTkSYRO/kL4ZqXOMEIznBKprxdiDsnPhA6bJN5tCTBexjKEn3SVUOMYGclCTzVOg9woxqkRHECiwL4sZZBtKOKhTV7ChpGiImEJWj/GUjB0m/3LlSl8bsHzLdV8dlJm8vusKl+kYYTfOxTYvW5Jotf6fNWrawm9NMJiORd81mkvMgY7xLNNOpzikJMpiqMl4rmcmLGyLSM5X2BCYLV6mvTFmpnfIq4T8BKsgVYhFQWbxbkGIYOGnOz3HiNKc7C2owt1AULaTMZ8HOCLyFPVOkHStp9+KJQtapdGVwnCErX2q/bX7mljQ16RHjKMqcvs2R47SkT22H0ET+cag0vOhNj4rUpoLMSk6NqnfCdD6pWtVmm7uqVrfK1a561S0BAQA7" /></div><p align="justify">И всего-то! Но у меня плохие новости для вас. Это были только те случаи, когда соседний узел чёрный. Если сосед красный, всё гораздо хуже. Однако есть и хорошие новости. Дальнейшие рассуждения представляют собой больше теоретический интерес. Вскоре мы научимся полностью избегать случая с красным соседом. Сделайте глубокий вдох и приготовьтесь ко второй половине повествования о случаях перебалансировки при удалении.</p><p align="justify">Готовы? Каждый случай с красным соседом требует по меньшей мере одного поворота, но не более трёх. Но сначала простые случаи. Если сосед красный и оба его дочерних узла чёрные, для восстановления баланса нам нужно сделать один поворот, перекрасить нового родителя в чёрный цвет, правый дочерний узел соседа - в красный. Запомните, при том, что описание и диаграммы иллюстрируют удаление из правого поддерева, случаи симметричны и при применении трюка с индексами направления мы обрабатываем один из таких симметричных случаев (где dir будет равен 1). Это должно быть ясно при взгляде на код:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Изучение диаграммы проясняет, как восстанавливается баланс при исправлении чёрных высот, но превращение узла 2 в красный узел сбивает с толку. Ну, где-то необходим красный, или это повлияет на чёрную высоту дерева, а единственный узел, который мы можем сделать красным с уверенностью - этот конкретный дочерний узел. Я уже не очень помню, как я пришёл к случаям с красными соседями, но я положительно уверен, что здесь не обошлось без спиртного. :-) Второй случай с красным соседом нацелен на внутренний дочерний узел соседа. Этот дочериний узел не может быть NULL-указателем (упражнение: почему?), поэтому мы просто проверяем один из этих узлов и дальше наши действия зависят от того, какой из них красный. Если красный - внешний дочерний узел, мы делаем двойной поворот, окрашиваем новый родительский узел в чёрный цвет, его правый дочерний узел - в чёрный цвет, а левый - в красный:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Это позволяет восстановить чёрную высоту правого поддерева без изменения чёрной высоты левого поддерева. Когда у нас два красных узла, легко один из них сделать чёрным после поворота, а второй оставить, как есть, чтобы избежать нарушения чёрной высоты поддерева, из которого мы и взяли узел. Последний случай, когда правый дочерний узел узла 3 - красный. Хорошие новости в том, что мы можем свести этот случай к предыдущему одиночным поворотом на узле 2, а затем - двойной поворот на узле 5 (предыдущий случай) даст нам ту структуру, которая нам и нужна с такими же цветами, как выше.</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Прежде, чем вы начнёте размышлять, что за мешанина должна быть в коде remove_balance(), давайте посмотрим на неё и убедимся, что всё не так уж и плохо. Да, функция длинная, но ни один из случаев не усложняет её до предела. Экономия получена из сходства разных случаев и назначения цветов вне кода их обработки, с целью избежать повторов кода, но мы можем сделать ещё лучше, как вы скоро увидите. Сравните случаи, описанные выше с их обработкой в исходном коде. Все ли случаи здесь обрабатываются?</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 struct node *remove_balance (struct node *root, int dir, int *done)
2 {
3 struct node *p = root;
4 struct node *s = root->link [!dir];
5
6 if (s != NULL && !is_red (s)) {
7 /* Black sibling cases */
8 if (!is_red (s->link [0]) && !is_red (s->link [1])) {
9 if (is_red (p))
10 *done = 1;
11 p->red = 0;
12 s->red = 1;
13 }
14 else {
15 int save = root->red;
16
17 if (is_red (s->link [!dir]))
18 p = rot_single (p, dir);
19 else
20 p = rot_double (p, dir);
21
22 p->red = save;
23 p->link [0]->red = 0;
24 p->link [1]->red = 0;
25 *done = 1;
26 }
27 }
28 else if (s->link [dir] != NULL) {
29 /* Red sibling cases */
30 struct node *r = s->link [dir];
31
32 if (!is_red (r->link [0]) && !is_red (r->link [1])) {
33 p = rot_single (p, dir);
34 p->link [dir]->link [!dir]->red = 1;
35 }
36 else {
37 if (is_red (r->link [dir]))
38 s->link [dir] = rot_single (r, !dir);
39 p = rot_double (p, dir);
40 s->link [dir]->red = 0;
41 p->link [!dir]->red = 1;
42 }
43
44 p->red = 0;
45 p->link [dir]->red = 0;
46 *done = 1;
47 }
48
49 return p;
50 }</pre></span><p align="justify">Обычно, красно-чёрные деревья сводят случай красного соседа к случаю чёрного соседа. Если вы не знакомы со случаем красного соседа (почему я вам его и показал), смысл такой редукции может показаться чрезвычайно запутанным. Однако, так как родительский узел соседа и дочерные узлы должны быть чёрными, одиночный поворот сместит родительский узел вниз на ту сторону, где мы произвели удаление, затем он окрашивается в красный цвет, а сосед и его дочерние узлы поднимаются вверх, причём сосед становится чёрным. Это не приводит к изменению чёрной высоты дерева, но просто изменяет сторону, на которой находится нарушение. Смещая нарушение вниз, мы можем гарантировать, что новый соседний узел (2 в этом случае) чёрный:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Здесь вещи становятся запутаннее, потому что нам дважды надо менять направление. На этом этапе мы рекурсивно поднимаемся вверх по дереву, но чтобы сократить случай с красным соседом, нам надо спуститься вниз вместе с соседним узлом. Пусть * - это место, где мы удалили узел, а узел 2 - новый сосед. После этого случаи идентичны, но нам следует соблюдать осторожность, чтобы спуститься вниз, ничего не потеряв, и без проблем вернуться обратно:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 struct node *remove_balance (struct node *root, int dir, int *done)
2 {
3 struct node *p = root;
4 struct node *s = root->link [!dir];
5
6 /* Case reduction, remove red sibling */
7 if (is_red (s)) {
8 root = rot_single (root, dir);
9 s = p->link [!dir];
10 }
11
12 if (s != NULL) {
13 if (!is_red (s->link [0]) && !is_red (s->link [1])) {
14 if (is_red (p))
15 *done = 1;
16 p->red = 0;
17 s->red = 1;
18 }
19 else {
20 int save = p->red;
21 int new_root = (root == p);
22
23 if (is_red (s->link [!dir]))
24 p = rot_single (p, dir);
25 else
26 p = rot_double (p, dir);
27
28 p->red = save;
29 p->link [0]->red = 0;
30 p->link [1]->red = 0;
31
32 if (new_root)
33 root = p;
34 else
35 root->link [dir] = p;
36
37 *done = 1;
38 }
39 }
40
41 return root;
42 }</pre></span><p align="justify">Перемещения root и p могут запутать, но единственно, чем здесь можно помочь - трассировка выполнения кода, чтобы быть уверенным в понимании почему root и p используются там, где они используются. Помните, я говорил, что сокращение случая запутано (возможно потому, что никто не берёт на себя труд объяснить это), но такое сокращение значительно упрощает код, потому что половину кода, выполняющего нужную нам работу по перебалансировке, можно выкинуть.</p><a name="section3"></a><h4>Вставка сверху вниз <a href="#top">[наверх]</a></h4><p align="justify">Одно из главных преимуществ красно-чёрных деревьев состоит в том, что алгоритм вставки и удаления может быть написан в один проход сверху вниз, вместо алгоритма, который использует рекурсивный проход вниз, родительские указатели или явный стек для обеспечения возможности пройти назад вверх, чтобы провести перебалансировку. Алгоритмы, работающие снизу вверх кажутся не элегантными, потому что они часто игнорируют полезную работу, которую можно выполнить на пути вниз или при подъёме обратно вверх. Если мы убедимся, что все цвета изменены и проделаны необходимые вращения, нам не нужно подниматься снова вверх и проводить перебалансировку. Это значит, что нам больше не нужна рекурсия или другие трюки, которые делают алгоритм запутаннее. Давайте рассмотрим нерекурсивный алгоритм вставки сверху вниз.</p><p align="justify">Ключевая идея, которую вы должны извлечь из этой статьи - то, что правила одинаковы, вне зависимости, как вы их реализуете. Мы по прежнему имеем дело с тремя случаями при вставке узла в красно-чёрное дерево: отражение цветов, одинарный и двойной поворот. Всё остальное - это каркас и детали. Для вставки сверху вниз рекурсия не даёт выигрыша. Чтобы обойтись без неё, нам необходимо аккуратно сохранять различные указатели, которые выведут нас обратно вверх по дереву. По крайней мере, у нас есть текущий узел в пути (в конце пути - указатель на NULL), его родительский узел и родитель родителя.</p><p align="justify">Чтобы избежать особых случаев, мы также использует в качестве корня дерева пустышку, при этом настоящий корень дерева является правым дочерним узлом этого номинального корня-пустышки. Т.о., нам не нужно постоянно отслеживать особые случаи на границах при перебалансировке на корне. Чтобы сделать вращение проще, мы также добавим указатель на родительский узел дедовского узла (<i>не знаю, как лучше без лишних слов передать это, поэтому пусть будет просто дедовским узлом</i>). Т.е., мы сохраняем по крайней мере три последних уровня нашего пути.</p><p align="justify">В случае с заменой цветов (color flip) мы проверяем дочерние узлы текущего узла. Если они красные, то текущий узел должен быть чёрным и мы проводим замену цвета. Это может вызвать красное нарушение далее вверх по дереву (вот почему мы сохранили указатель на родителя текущего узла). Теперь учитывая то, что замена цветов сразу приведёт к нарушению, мы прямиком приступаем к проверке нарушений между родительским узлом и текущим. Если оба красные, мы совершаем одинарный или двойной поворот вокруг дедовского узла:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Обратите внимание, как происходит вращение вокруг дедовского узла. Исходя из этих особенностей мы и сохраняем указатель на родителя дедовского узла. В противном случае было бы куда сложнее "оповестить" дерево о тех изменениях, которые мы вносим. Код, который творит всё волшебство короче, чем вы могли бы ожидать, но он не следует обычным методикам. Вся работа проделывается в цикле при нисхождении, включая также операцию вставки, т.к. вставка нового красного узла может вызвать красное нарушение. Цикл разрывается, если найдены данные или вставлен новый узел или в случае, если в дереве уже был узел с такими данными. Т.о., алгоритм, представленный ниже не допускает дубликаты и не предупреждает о них:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int insert (struct tree *tree, int data)
2 {
3 if (tree->root == NULL) {
4 /* Empty tree case */
5 tree->root = make_node (data);
6 if (tree->root == NULL)
7 return 0;
8 }
9 else {
10 struct node head = { 0 }; /* False tree root */
11
12 struct node *g, *t; /* Grandparent & parent */
13 struct node *p, *q; /* Iterator & parent */
14 int dir = 0, last;
15
16 /* Set up helpers */
17 t = &head;
18 g = p = NULL;
19 q = t->link [1] = tree->root;
20
21 /* Search down the tree */
22 for ( ; ; ) {
23 if (q == NULL) {
24 /* Insert new node at the bottom */
25 p->link [dir] = q = make_node (data);
26 if (q == NULL)
27 return 0;
28 }
29 else if (is_red (q-> link [0]) && is_red (q->link [1])) {
30 /* Color flip */
31 q->red = 1;
32 q->link [0]->red = 0;
33 q->link [1]->red = 0;
34 }
35
36 /* Fix red violation */
37 if (is_red (q) && is_red (p)) {
38 int dir2 = t->link [1] == g;
39
40 if (q == p->link [last])
41 t->link [dir2] = rot_single (g, !last);
42 else
43 t->link [dir2] = rot_double (g, !last);
44 }
45
46 /* Stop if found */
47 if (q->data == data)
48 break;
49
50 last = dir;
51 dir = q->data < data;
52
53 /* Update helpers */
54 if (g != NULL)
55 t = g;
56 g = p, p = q;
57 q = q->link [dir];
58 }
59
60 /* Update root */
61 tree->root = head.link [1];
62 }
63
64 /* Make root black */
65 tree->root->red = 0;
66
67 return 1;
68 }</pre></span><p align="justify">Такая вставка сверху вниз не столь красива, как рекурсивная вставка снизу вверх, однако алгоритм использует все преимущества, предоставляемые свойствами красно-чёрных деревьев и избегает потенциальных проблем рекурсивного алгоритма. Теоретически этот алгоритм более эффективен. :-) Хорошая новость: удаление сверху вниз проще, чем снизу вверх, и мы можем использовать уроки, извлечённые из изучения алгоритма вставки, чтобы сделать нашу реализацию проще. Давайте посмотрим.</p><a name="section4"></a><h4>Удаление сверху вниз <a href="#top">[наверх]</a></h4><p align="justify">Удаление снизу вверх было проблемой, т.к. нам приходилось восстанавливаться после чёрных нарушений, которые, как мы узнали, сложнее исправить из двух видов нарушений. Однако, удаление было тривиальным, если мы удаляли красный узел, потому что удаление красного узла не нарушало правил красно-чёрного дерева. Если бы мы каким-то образом смогли гарантировать, что удаляемый узел красного цвета, это бы существенно упростило операцию удаления. Хорошая новость: мы можем гарантировать это! Удерживая цвет узла в верхушке дерева красным и сдвигая его вниз, используя замену цветов с вращениями, мы всегда сможем гарантировать, что будет удалён красный узел.</p><p align="justify">Есть только четыре случая сдвига красного узла вниз по дереву без чёрного нарушения. Первый случай - простое обратное отражение цвета. Если узел и его сосед - чёрные, и все четыре их дочерних узла - чёрные, родительский узел перекрашивается в чёрный цвет, а оба его потомка - в красный:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">В принципе, это обратное отражение цветов (reverse color flip), которое мы делали во время вставки. Это сдвигает красные узлы вниз без изменения чёрной высоты дерева, вместо того, чтобы удерживать их наверху. Здорово. :-) Следующий случай - случай с красным соседом, который мы быстро сократим спомощью одинарного поворота, используя трюки, которые мы изучили на примере удаления снизу вверх. Этот случай довольно интуитивен теперь, когда нам не нужно менять направление, чтобы всё работало. Заметьте, что ни чёрная высота, ни цвет родительского узла не изменяются. Т.к. мы двигаемся вниз, сдвигание и соседних узлов вниз - то, что нужно:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">И наконец, как вы наверно догадались, два случая - одиночный и двойной поровот в зависимости от цвета дочерних узлов соседа. Эти случаи вам уже знакомы (должны быть!), поэтому мы лишь вкратце по ним пройдёмся, прежде чем перейдём к коду. Если правый дочерний узел красный, мы делаем двойной поворот. Однако, в этом случае изменением цвета в rot_single() дело не ограничивается. Для пущей аккуратности мы форсируем правильный цвет во всех затронутых узлах:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Как обычно, мы убеждаемся, что цвет родительского узла не изменяется и чёрная высота обоих поддеревьев остаётся такой же, как исходная высота. Эти два правила помогают избежать красного и чёрного нарушения. Если левый дочерний узел соседа красный, то одинарного поворота более чем достаточно, чтобы сделать то же самое. Т.к. окончательные цвета те же, то перекрашивание в этом случае суммируется с предыдущим случаем:</p><div align="center"><img alt="" src="data:image/gif;base64,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" /></div><p align="justify">Теперь весёлая часть. Алгоритм будет использовать осторожное удаление, причём узел, который должен быть удалён, сохраняется, а цикл продолжается, пока не найден узел-предшественник (<i>младший узел, содержащий значение, по порядку предшествующее тому, которое хранит удаляемый узел</i> - перев.). На этом цикл прохода вниз завершается, копируются данные и внешние узлы сращиваются с деревом в противоположность обычному удалению копированием, когда поиск останавливается при нахождении узла, а для нахождения узла-предшественника (младшего узла) используется дополнительный шаг. На пути вниз мы также проверяем описанные выше случаи и обрабатываем их:</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> 1 int remove (struct tree *tree, int data)
2 {
3 if (tree->root != NULL) {
4 struct node head = { 0 }; /* False tree root */
5 struct node *q, *p, *g; /* Helpers */
6 struct node *f = NULL; /* Found item */
7 int dir = 1;
8
9 /* Set up helpers */
10 q = &head;
11 g = p = NULL;
12 q->link [1] = tree->root;
13
14 /* Search and push a red down */
15 while (q->link [dir] != NULL) {
16 int last = dir;
17
18 /* Update helpers */
19 g = p, p = q;
20 q = q->link [dir];
21 dir = q->data < data;
22
23 /* Save found node */
24 if (q->data == data)
25 f = q;
26
27 /* Push the red node down */
28 if (!is_red (q) && !is_red (q->link [dir])) {
29 if (is_red (q->link [!dir]))
30 p = p->link [last] = rot_single (q, dir);
31 else if (!is_red (q->link [!dir])) {
32 struct node *s = p->link [!last];
33
34 if (s != NULL) {
35 if (!is_red (s->link [!last]) && !is_red (s->link [last])) {
36 /* Color flip */
37 p->red = 0;
38 s->red = 1;
39 q->red = 1;
40 }
41 else {
42 int dir2 = g->link [1] == p;
43
44 if (is_red (s->link [last]))
45 g->link [dir2] = rot_double (p, last);
46 else if (is_red (s->link [!last]))
47 g->link [dir2] = rot_single (p, last);
48
49 /* Ensure correct coloring */
50 q->red = g->link [dir2]->red = 1;
51 g->link [dir2]->link [0]->red = 0;
52 g->link [dir2]->link [1]->red = 0;
53 }
54 }
55 }
56 }
57 }
58
59 /* Replace and remove if found */
60 if (f != NULL) {
61 f->data = q->data;
62 p->link [p->link [1] == q] =
63 q->link [q->link [0] == NULL];
64 free (q);
65 }
66
67 /* Update root and make it black */
68 tree->root = head.link [1];
69 if (tree->root != NULL)
70 tree->root->red = 0;
71 }
72
73 return 1;
74 }</pre></span><p align="justify">Функция, реализующая алгоритм, короткая и красивая, хотя, пожалуй многочисленные отступы сделают код несколько безвкусным в чьих-то глазах. В качестве упражнения попытайтесь уменьшить код. :-) Как и в случае со вставкой мы использовали корень-пустышку, чтобы избежать особых случаев, а также несколько вспомогательных переменных, содержащих указатели на верхние узлы дерева. Основная сложность состоит в обработке тех случаев, что требуют перебалансировки. Если отбросить эту часть кода, то вы обнаружите алгоритм простого удаления из бинарного дерева поиска. Оттрассируйте выполнение кода функции, чтобы лучше понять, как она работает, потому что это очень изящный алгоритм. К тому же, я никогда не видел, чтобы его использовали прежде, что означает, я мог быть первым, кто его открыл. :-)</p><p align="justify">Это и есть удаление сверху вниз и его можно сравнить с удалением снизу вверх, так же, как и вставку, но теперь мы можем судить о том, что код операции удаления сверху вниз короче и (я думаю) проще. Красно-чёрные деревья проще поддаются пониманию при использовании алгоритмов, работающих сверху вниз, но к сожалению на фоне того, что единственный широко известный источник, который даёт код для операции удаления - это CLR, большая часть реализаций использует алгоритмы, работающие снизу вверх. Ну, ладно.</p><a name="section5"></a><h4>Заключение <a href="#top">[наверх]</a></h4><p align="justify">Красно-чёрные деревья - интересная вещь. Есть мнение, что они проще AVL-деревьев (их прямой конкурент), и на первый взгляд это действительно так, потому что вставка здесь - простая операция. Однако, когда кто-то начинает разбираться с алгоритмом удаления, красно-чёрные деревья становятся очень запутанными. В противовес этой сложности стоит отметить, что и вставка и удаление могут быть реализованы в однопроходном алгоритме, работающем сверху вниз. В случае с AVL-деревьями это не так, потому что там только вставка может идти сверху вниз. Удаление из AVL-дерева требует алгоритма, работающего снизу вверх.</p><p align="justify">Где использовать красно-чёрные деревья? На самом деле, это зависит от вас, но я обнаружил, что наилучшим образом красно-чёрные деревья показывают себя при больших потоках случайных данных, со случайными вырожденными последовательностями и при поиске без локальных ссылок. При это полностью используется то преимущество, что для балансировки красно-чёрного дерева производится минимальная работа по сравнению с AVL-деревьями, но при этом возможна высокая скорость поиска.</p><p align="justify">Красно-чёрные деревья популярны, как и большинство структур данных с причудливыми названиями. Например в Java и C++ библиотечные структуры отображения (map) обычно реализуются, как красно-чёрные деревья. По скорости красно-чёрные деревья сравнимы с AVL-деревьями. Хотя, сбалансированность их не так высока, но работа по поддержанию сбалансированности в красно-чёрных деревьях обычно эффективнее. Есть определённые неправильные представления о красно-чёрных деревьях, но в целом, их слава оправдана.</p><p align="justify">В этой статье описана красно-чёрная абстракция, используемая для балансировки бинарного дерева поиска. Рассмотрены обе операции - вставка и удаление, работающие как сверху вниз, так и снизу вверх, приведены реализации каждой из них. Чтобы получить больше информации по красно-чёрным деревьям, поищите описание библиотеки libavl в превосходной онлайн-книге Бена Пфаффа (если бы она была у меня, когда я ломал голову над тем, что описано выше!) или обзаведитесь книгой “Введение в алгоритмы” Кормена, Лейзерсона и Ривеста.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com1tag:blogger.com,1999:blog-3876463594612412036.post-46367081677305127452010-04-27T06:30:00.000-07:002010-04-27T07:01:36.713-07:00Ещё немного о потоках ядра: kthreadd<p align="justify">ещё буквально пара слов о потоках ядра. Вероятно, Вы замечали в выводе ps -ef поток ядра kthreadd. Наверняка, у Вас даже возникал вопрос, для чего он нужен? На самом деле, всё достаточно просто. Опосредованно взаимодействуя с помощью определённых API с данным потоком, различные части ядра могут ставить в очередь на создание новые потоки, которые и создаёт kthreadd. Данные API ядра используются наряду с функцией kernel_thread(), с тем отличием, что создание нового процесса происходит не сразу же. Сам поток kthreadd стартует после инициализации основного потока ядра в функции rest_init() init/main.c:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>static noinline void __init_refok rest_init(void)
__releases(kernel_lock)
{
int pid;
rcu_scheduler_starting();
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
<i>pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);</i>
unlock_kernel();
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
preempt_disable();
/* Call into cpu_idle with preempt disabled */
cpu_idle();
}</pre></span>Описатель задачи (struct task_struct) kthreadd хранится в переменной ядра kthreadd_task. Заметьте, что функция kernel_thread() возвращает идентификатор нового процесса (потока). Для того, чтобы получить описатель задачи, в ядре, в частности в приведённом коде, используется функция find_task_by_pid_ns(), первый аргумент которой - идентификатор потока, чей описатель задачи нам нужен, а второй - пространство идентификаторов процесса-предка (в данном случае - init).</p><p align="justify">Функция потока kthreadd реализована в kernel/kthread.c:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>int kthreadd(void *unused)
{
struct task_struct *tsk = current;
/* Setup a clean context for our children to inherit. */
set_task_comm(tsk, "kthreadd");
ignore_signals(tsk);
set_cpus_allowed_ptr(tsk, cpu_all_mask);
set_mems_allowed(node_possible_map);
current->flags |= PF_NOFREEZE | PF_FREEZER_NOSIG;
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list))
schedule();
__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock);
while (!list_empty(&kthread_create_list)) {
struct kthread_create_info *create;
create = list_entry(kthread_create_list.next,
struct kthread_create_info, list);
list_del_init(&create->list);
spin_unlock(&kthread_create_lock);
create_kthread(create);
spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
}
return 0;
}</pre></span>Если не вдаваться сейчас во все детали, то из кода видно, что kthreadd() "крутится" в вечном цикле, в начале каждого прохода проверяя состояние списка kthread_create_list. Если список пуст, то поток устанавливает своё состояние как "спящий" и отдаёт управление, вызывая функцию планировщика schedule(). Если в списке есть элементы, то поток взводит спин-блокировку на списке, чтобы обезопасить его от изменений и до тех пор, пока список не пуст последовательно выполняет следующие действия:<ol><li>в переменную create типа struct create_thread_nfo* получаем элемент списка;</li><li>удаляем элемент из списка;</li><li>снимаем спин-лок со списка, так что теперь в него снова можно добавлять новые элементы извне;</li><li>используя данные, находящиеся по адресу, сохранённому в create, создаём новый поток с помощью вспомогательной функции create_kthread() (не путать с kthread_create() и kernel_thread()! в отличие от них, create_kthread() не экспортируется за пределы kthread.o);</li><li>ну и наконец снова взводим спин-лок на списке, чтобы не произошло ничего неожиданного, пока мы будем проверять пуст ли список потоков к созданию :)</li></ol></p><p align="justify">Очередь потоков к созданию - это нечто иное, как двусвязный список, о ктором я уже писал. Вот как выглядит структура-элемент этого списка:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>struct kthread_create_info
{
/* Information passed to kthread() from kthreadd. */
int (*threadfn)(void *data);
void *data;
/* Result passed back to kthread_create() from kthreadd. */
struct task_struct *result;
struct completion done;
struct list_head list;
};</pre></span>Самые интересные на данный момент поля здесь - это threadfn - указатель на функцию, которая должна выполняться в отдельном потоке, data - указатель на данные, которые будут использоваться потоком, result - указатель на описатель задачи для нового потока.</p><p align="justify">В список новые элементы добавляются с помощью функции kthread_create():<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>/**
* kthread_create - create a kthread.
* @threadfn: the function to run until signal_pending(current).
* @data: data ptr for @threadfn.
* @namefmt: printf-style name for the thread.
*
* Description: This helper function creates and names a kernel
* thread. The thread will be stopped: use wake_up_process() to start
* it. See also kthread_run(), kthread_create_on_cpu().
*
* When woken, the thread will run @threadfn() with @data as its
* argument. @threadfn() can either call do_exit() directly if it is a
* standalone thread for which noone will call kthread_stop(), or
* return when 'kthread_should_stop()' is true (which means
* kthread_stop() has been called). The return value should be zero
* or a negative error number; it will be passed to kthread_stop().
*
* Returns a task_struct or ERR_PTR(-ENOMEM).
*/
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,
const char namefmt[],
...)
{
struct kthread_create_info create;
create.threadfn = threadfn;
create.data = data;
init_completion(&create.done);
spin_lock(&kthread_create_lock);
list_add_tail(&create.list, &kthread_create_list);
spin_unlock(&kthread_create_lock);
wake_up_process(kthreadd_task);
wait_for_completion(&create.done);
if (!IS_ERR(create.result)) {
struct sched_param param = { .sched_priority = 0 };
va_list args;
va_start(args, namefmt);
vsnprintf(create.result->comm, sizeof(create.result->comm),
namefmt, args);
va_end(args);
/*
* root may have changed our (kthreadd's) priority or CPU mask.
* The kernel thread should not inherit these properties.
*/
sched_setscheduler_nocheck(create.result, SCHED_NORMAL, ¶m);
set_cpus_allowed_ptr(create.result, cpu_all_mask);
}
return create.result;
}
EXPORT_SYMBOL(kthread_create);</pre></span>kthread_create() принимает указатель на функцию, которая должна выполняться в отдельном потоке (threadfn), указатель на данные для потока (data) и имя нового потока (namefmt). Сперва kthread_create() инициализирует поля переменной create типа struct kthread_create_info. Затем на время добавления нового элемента в список потоков, "ждущих" создания, kthread_create() взводит спин-лок, чтобы никто больше не мог добавить новые элементы и внести сумятицу в наши дела :) Новый элемент списка добавляется в хвост с помощью макроса list_add_tail(). Затем спин-лок снимается - список снова свободен. Далее, kthread_create() будит поток kthreadd с помощью wake_up_process(), который должен будет проверить очередь и запустить новый поток, как описывалось выше. Ну и наконец, если при создании нового потока не возникло ошибок, то подготавливаем такие реквизиты нового потока, как имя и параметры планирования. Оставив новый поток в состоянии сна, возвращаем управления. Вот и всё, что делает kthread_create(). Если кратко, то она ставит в очередь новый запрос на создание потока, дожидается, пока не отработает рабочий поток kthreadd и не будет создан новый спящий поток.</p><p align="justify">На этом матрёшка не заканчивается. В упомянутой функции kthreadd() мы сознательно пропустили одно место:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> spin_unlock(&kthread_create_lock);
create_kthread(create);
spin_lock(&kthread_create_lock);</pre></span>create_kthread() - ещё одна вспомогательная внутренняя функция, которая с помощью уже знакомого нам вызова kernel_thread() создаёт реальный поток. Но, не всё так просто. На самом деле, здесь создаётся не тот поток, который указывался в качестве аргумента threadfn для kthread_create()! Создаётся всего лишь новый поток kthread - опять же, внутренняя неэкспортируемая за пределы единицы трансляции функция :)
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>static void create_kthread(struct kthread_create_info *create)
{
int pid;
/* We want our own signal handler (we take no signals by default). */
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
if (pid < 0) {
create->result = ERR_PTR(pid);
complete(&create->done);
}
}</pre></span>По результату, который будет положительным числом - идентификатором процесса (pid) в сучае успеха или отрицательным - код ошибки, узнаём, как всё прошло.</p><p align="justify">Что же делает kthread()? Не так и много. Сначала, новый поток копирует все необходимые данные из переданного ему аргумента _create - т.е., адрес функции потока (threadfn) и данные для потока (data). Во внутреннюю пемеременную self типа struct kthread записываем "состояние" потока - should_stop - не 0, если поток должен быть остановлен и exited - код возврата, если поток завершился.<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>static int kthread(void *_create)
{
/* Copy data: it's on kthread's stack */
struct kthread_create_info *create = _create;
int (*threadfn)(void *data) = create->threadfn;
void *data = create->data;
struct kthread self;
int ret;
self.should_stop = 0;
init_completion(&self.exited);
current->vfork_done = &self.exited;
/* OK, tell user we're spawned, wait for stop or wakeup */
__set_current_state(TASK_UNINTERRUPTIBLE);
create->result = current;
complete(&create->done);
schedule();
ret = -EINTR;
if (!self.should_stop)
ret = threadfn(data);
/* we can't just return, we must preserve "self" on stack */
do_exit(ret);
}</pre></span>Здесь:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> /* OK, tell user we're spawned, wait for stop or wakeup */
__set_current_state(TASK_UNINTERRUPTIBLE);
create->result = current;
complete(&create->done);
schedule();</pre></span>мы устанавливаем состояние потока в TASK_UNINTERRUPTIBLE (спящий процесс). В описатель задачи - result - записываем указатель на текущий контекст (ведь когда kthread была запущена через kernel_thread(), у нас уже свой контекст выполнения для данного экземпляра kthread()). Далее, сигнализируем о завершении инициализации описателя нового процесса и состояния задачи с помощью complete() (здесь я намеренно пока не углубляюсь в то, что такое атомарное ожидание). Просим ядро выполнить перепланирование процессов. В этом месте, по сути, выполнение нашего нового потока приостанавливается, т.к. планировщик не будет выделять ему процессорное время ввиду того, что поток спит... Следущие строки будут выполнены только после того, как поток будет разбужен:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre> ret = -EINTR;
if (!self.should_stop)
ret = threadfn(data);
/* we can't just return, we must preserve "self" on stack */
do_exit(ret);</pre></span>Тут, как будто, ничего мистического нет. Сразу, как только поток вновь получит процессор в своё владение (будет кем-то разбужен), в переменную-код возврата мы записываем код ошибки EINTR - "процесс прерван". Затем необходимо проверить, не успел ли кто-то отменить выполнение потока. Если нет, то наконец-то выполняем именно <strong>нашу</strong> функцию - threadfn(), передавая ей в качестве аргумента данные для работы. Ну а после этого - делаем do_exit() после того, как функция threadfn() возратит управление.</p><p align="justify">Такова подсистема поочередного запуска потоков в общих чертах. Всё остальное достаточно просто:<ul><li><strong>void kthread_bind(struct task_struct *k, unsigned int cpu)</strong> - создать поток, привязанный к конкретному процессору;</li><li><strong>int kthread_stop(struct task_struct *k)</strong> - запросить останов потока;</li><li><strong>int kthread_should_stop(void)</strong> - проверка, был ли запрошен останов потока (удобно использовать внутри самого потока);</li><li><strong>kthread_run(threadfn, data, namefmt, ...)</strong> - макрос, который делает то же, что kthread_create() с той лишь разницей, что поток сразу будет пробуждён.</li></ul></p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com1tag:blogger.com,1999:blog-3876463594612412036.post-4105440303677952162010-04-12T15:47:00.000-07:002013-03-13T03:52:23.742-07:00Linux - Потоки ядра<div align="right">
<i>Когда-то всё повторится с вероятностью 99%,<br />но в других обстоятельствах, уже с другими людьми,<br />по другому поводу, но с той же целью –<br />стукнуть человека граблями по лбу,<br />чтобы не забылся человек. ©</i></div><br />
<div align="justify">
Пожалуй, пришло время обсудить ещё одну интересную тему, касающуюся ядра Linux. И тема эта – потоки ядра. Не станем тянуть кота за хвост, а сразу перейдём к делу. Во-первых, выясним для себя, что такое поток? Для того, чтобы ответить на этот вопрос, зададимся другим вопросом, связанным с первым. Что такое процесс? В современных мультизадачных операционных системах есть возможность запустить несколько приложений, которые будут работать какбы параллельно, не мешая друг другу. Можно, допустим, запустить плейер, слушать музыку и одновременно набивать текст в редакторе. Что-то вроде того, что делаю я сейчас :) Но само приложение, это, грубо говоря, всего лишь образ на диске, файл, который необходимо открыть и запустить программу действий, хранящихся в нём. Такие действия есть ничто иное, как инструкции процессора. Но это ещё не процесс. Процесс – некая сущность, абстрактная по своей природе, которой оперирует операционная система. Если посмотреть на классические определения, то процесс – это совокупность кода (инструкция процессора) и ресурсов, выделенных во владение данному коду. Ведь инструкции должны в свою очередь также чем-то оперировать, верно? Где-то необходимо хранить промежуточные и конечные результаты работы. По обстоятельствам, хранить их можно либо на диске (файлы), либо в оперативной памяти. В данной случае блоки памяти, выделенные в распоряжение коду и дескрипторы файлов (то, посредством чего процесс ссылается на объекты ядра - файлы) – это и есть ресурсы. Разумеется, всё многообразие ресурсов, которыми может владеть процесс, не исчерпывается памятью и файловыми дескрипторами. Оставим в покое такие тривиальные вещи, как память и файлы и добавим, что для процесса, т.е. выполняющегося кода, есть ещё один крайне важный ресурс – процессорное время. Каждый процесс имеет в своём распоряжении определённый квант времени, на протяжении которого он волен делать с процессором всё (ну, или точнее, почти всё), что угодно, в том числе, он может отказаться от своего кванта и отдать его другому процессу. Тот, кто занимается выделением квантов времени процессам, управляет очередью, в которой за временем стоят процессы и непосредственно ведает величиной кванта времени, который будет выделен тому или иному процессу – это ядро операционной системы. Если быть точнее, подсистема ядра, занимающаяся планированием процессов или попросту – планировщик (scheduler). Процессы, таким образом, работают отнюдь не одновременно и параллельно (для простоты отвлечёмся от того факта, что в последнее время широкое распространение получили SMP-системы, основанные на многоядерных процессорах, где истинный параллелизм выполнения кода действительно возможен). Вместо этого планировщик последовательно переключает процессы, давая возможность каждому из них на какое-то время воспользоваться центральным процессором в своих целях. Так как происходит это достаточно быстро, то создаётся иллюзия, что несколько процессов работают одновременно. Таким образом, процесс – это логическая сущность, нечто, что в данный момент выполняется. Однако, это не предельная сущность. В современных операционных системах почти повсеместно имеет место ещё одна сущность – поток. Поток, это приближённо говоря часть процесса, выполняющаяся параллельно другим его частям. Если мы возьмём такой классический пример, как GUI-приложение, то один поток в таком процессе может, допустим, отрисовывать пользовательский интерфейс, в то время, как второй поток будет заниматься действительно полезной работой :) Взаимодействуя друг с другом в таком процессе можно обеспечить актуальное состояние графического интерфейса, отражающего прогресс выполнения задачи – ну хоть отрисовывать прогресс-бар, к примеру :) В терминах многопоточного программирования у приложения всегда есть как минимум один поток – главный. Главный поток в процессе своей деятельности может порождать вторичные потоки. В зависимости от платформы поддержка потоков может быть реализована на уровне ядра операционной системы, либо сугубо средствами библиотек. Если поддержка процессов ядром неизбежна, то, как было сказано, потоки вовсе не обязаны поддерживаться данной ОС. Потоки очень похожи на процессы, но есть одно весьма важное отличие. Каждый процесс владеет своим набором ресурсов и ресурсы эти недоступны другим процессам, выполняющимся в системе. Так должно быть в идеале и так есть, за исключением тех случаев, когда приложения сами заинтересованы в разделении своих ресурсов (межпроцессное взаимодействие - IPC). С потоками дело обстоит иначе. Все потоки данного процесса разделяют общие ресурсы, которые имеются в распоряжении данного процесса – память, дескрипторы файлов, сокетов, и проч. После небольшого вводного курса обратимся к объекту наших изысканий – ядру Linux. Изначально в ядре Linux не было поддержки потоков. Вместо этого потоки реализовывались средствами стандартной библиотеки POSIX – pthread. С одной стороны, такой подход хорош тем, что устраняется зависимость приложения от конкретного ядра, а с другой, отсутствие поддержки со стороны ядра сильно усложняет реализацию самой библиотеки, ведь необходимо как-то обеспечить хитроумные механизмы выполнения потоков, межпоточной синхронизации и проч. Позже ситуация изменилась и в ядре появилась концепция так называемых облегчённых процессов (lightweight process). В отличие от ядра NT (то, которое Windows) в ядре Linux нет выделенной концепции потока. В качестве потока как единица планирования на уровне ядра выступает упомянутый облегчённый процесс. Если не вдаваться в детали, то облегчённый процесс подобен обычному, за исключением того, что ресурсы облегчённых процессов разделяются между собой. Т.о., облегчённый процесс и есть интерпретация концепции потока в рамках ядра Linux. Благодаря введению облегчённых процессов многие вещи упростились, т.к. теперь ядро способствует процессам в распараллеливании задач. Теперь упростилась реализация пользовательских библиотек для поддержки многопоточности, можно спокойно использовать объекты блокировки ядра, создавать пулы потоков и др. Теперь мы подошли к тому, чтобы ответить, что из себя представляют потоки ядра, чем они отличаются от пользовательских потоков, кому и зачем всё это вообще нужно? В принципе, потоки ядра очень похожи на пользовательские процессы за исключением того обстоятельства, что свои данные потоки ядра хранят в памяти самого ядра и, соответственно, имеют доступ к виртуальному адресному пространству ядра, они т.о. имеют доступ к функциям ядра и его структурам данных. Поток для ядра – это возможность запустить на фоне определённую задачу, которая будет выполняться не непрерывно, а, допустим, ожидая наступления какого-то события, будет «спать», т.е., асинхронно. Как и в случае с процессами, потоки какое-то время способны владеть процессом, пока они не будут вытеснены другим потоком, что имеет место в случае т.н. вытесняющей многозадачности. Ниже мы рассмотрим, как используются потоки ядра и что они делают на конкретных примерах и с привлечением сведений о том, что такое очереди выполнения, ожидания и состояния процесса.</div>
<div align="justify">
Для начала посмотрим, что есть в нашей системе на базе ядра Linux ветки 2.6. Сделать это просто, если запустить ps с ключами “e” и “f”:<span style="color: #000099; font-family: courier new;"></span></div>
<pre><span style="color: #000099; font-family: courier new;">$ ps –ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 22:36 ? 00:00:00 init [3]
root 2 1 0 22:36 ? 00:00:00 [ksoftirqd/0]
root 3 1 0 22:36 ? 00:00:00 [events/0]
root 38 3 0 22:36 ? 00:00:00 [pdflush]
root 39 3 0 22:36 ? 00:00:00 [pdflush]
…
root 4066 4015 0 22:59 tty3 00:00:00 ps -ef
</span></pre>
В приведённом листинге потоки ядра это те, чьи имена взяты в квадратные скобки. Допустим, поток ядра ksoftirqd занимается обеспечением системы обработки мягких прерываний по IRQ. Суть идеи состоит в том, что как известно, в ядре Linux реализована следующая модель прерываний. Обработчик прерывания имеет две «половины» - верхнюю и нижнюю. Верхняя половина прерывания требует сиюминутной обработки, в то время, как обработка нижней половины может быть отложена и её ядро не обязано обработать мгновенно. Откладывая т.о. выполнение части кода, отвечающей за обработку прерываний, система экономит время, которое может быть отдано под выполнение более приоритетных на данный момент задач. В функции потока ksoftirqd входит наблюдение за тем, чтобы все запросы на прерывания были обслужены с одной стороны, и с другой, чтобы система не загружалась до предела такими запросами. Номер после слэша – это число, идентифицирующее поток, т.к. в SMP-системах на каждом процессоре выполняется свой экземпляр потока ksoftirqd. Если в Вашей системе 4 процессора, либо 4-соответственно в системе будут 4 потока ksoftirqd – ksoftirqd/0-ksoftirqd/3, которые будут привязаны каждый к своему процессору или процессорному ядру. Ещё один поток ядра – events, обеспечивает централизованную поддержку рабочих очередей. Если какая-то часть ядра желает отложить выполнение работы, то эта часть может либо создать собственную рабочую очередь, либо поставить задание в очередь, созданную потоком events. Ещё один пример того, как ядро создаёт потоки для фоновых задач – pdflush. Так как известно, что по быстродействию память неизмеримо лучше, чем диск, то в целях соблюдения баланса производительности данные, предназначенные для записи на накопитель не попадают туда мгновенно. Вместо этого данные по запросам записи накапливаются в кэше. pdflush как раз и занимается синхронизацией буферного кэша с содержимым диска. Поток pdflush сбрасывает на диск т.н. «грязные» страницы кэша, т.е. те, в которые производилась запись. При этом pdflush следит, чтобы объём свободной памяти не слишком понижался и чтобы страницы с данными не находились в кэше больше определённого времени. Если объём доступной памяти уменьшается, а в кэше находятся данные, ожидающие записи или если некоторые страницы задержались, то pdflush обеспечивает запись кэшированных данных на диск. Из ранее приведённого листинга видно, что в системе есть 2 экземпляра pdflush, но без номеров, которые бы указывали на привязку к процессорам. На самом деле, новые экземпляры потока pdflush создаются по мере роста нагрузки на систему ввода/вывода, либо когда один поток сильно загружен. khubd – часть подсистемы USB ядра, которая отслеживает подключение к USB-хабу устройств и занимается конфигурированием устройств, поддерживающий горячее подключение (hot-plugged). Наконец, kjournald – это поток журналирования, используемый ядром в реализация файловых систем с поддержкой журналирования, как например ext3.<br />
<div align="justify">
Для того, чтобы лучше понять суть сказанного, попробуем реализовать свой собственный поток ядра и начнём с постановки задачи: допустим, нам нужен такой поток, который бы вёл наблюдение за состоянием ядра и асинхронно с помощью вспомогательной пользовательской программы уведомлял Вас, что определённые структуры ядра находятся в состоянии, не вполне хорошем или неудовлетворительном. Как например ситуация, когда свободное место в принимающем буфере сетевого драйвера почти иссякло. Чтобы решить поставленную задачу необходимо следующее:</div>
<ol>
<li>код должен быть оформлен, как фоновое задание, которое ожидает наступления некого асинхронного события;</li>
<li>наш код должен иметь доступ к структурам данных ядра, ведь определение критического уровня буфера выполняется другими частями ядра;</li>
<li>наш код должен вызывать вспомогательное пользовательское приложение, что затратно в плане времени.</li>
</ol>
<br />
<div align="justify">
Наш поток освобождает процессор и пробуждается только тогда, когда наступает ожидаемое событие, например, изменение в структуре, за которой мы ведём наблюдение. Пробудившись, поток должен вызвать вспомогательное приложение, которое и обязано уведомить пользователя.</div>
Приведённый далее вызов создаёт поток ядра:<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;">ret = kernel_thread (mykthread, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);</span></pre>
Наш поток можно создать там, где это наиболее подходит. Ну, хотя бы в init/main.c. Набор флагов, передаваемый функции kernel_thread(), определяет, какие ресурсы должны разделяться между родителем и его потоками-потомками.<br />
<div align="justify">
CLONE_SIGHAND – предписывает сделать общими обработчики сигналов Unix;<br />CLONE_FILES – разделять дескрипторы открытых файлов;<br />CLONE_FS – родитель и потомок используют общую информацию о файловой системе. Сюда входит корневой каталог, текущий каталог и параметр umask процесса. Любой вызов chroot(2), chdir(2), umask(2), выполненный либо потомком, либо родителем влияет также и на другой процесс.</div>
В коде, приводимом далее, функция ядра daemonize() создаёт поток без ассоциированных с ним пользовательских ресурсов. Следующий вызов reparent_to_init() изменяет родителя взывающего потока на поток init. Каждый поток (равно, как и процесс) в Linux (да и не только в нём) имеет единственного родителя. В случае, когда родительский процесс завершается, не дождавшись окончания работы своего дочернего потока (процесса), такой осиротевший процесс становится зомби. Проще говоря, процесс-зомби уже не планируется на выполнение, но в то же время запись о процессе, поставленная ему в соответствие и описывающая его не удаляется. Вся информация сохраняется, хотя, по существу, процесс уже завершён и больше никому не нужен. Переназначение процесса-родителя позволяет избежать таких неприятностей. В ядрах ветки 2.6 функция daemonize() сама вызывает reparent_to_init(). Так как вызов daemonize() по умолчанию блокирует все сигналы, нам необходимо вызвать функцию allow_signal(), чтобы указать, какие сигналы можно доставлять нашему потоку. Т.к. в ядре нет обработчиков сигналов, как например, в glibc, необходимо использовать специальную функцию signal_pending(), с помощью которой можно определить, был ли потоку послан сигнал и если да, то обработать его соответственно. В нашем примере мы обрабатываем только SIGKILL, по которому поток завершается.<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;">static DECLARE_WAIT_QUEUE_HEAD (myevent_waitqueue);
rwlock_t myevent_lock;
static int mykthread (void *unused)
{
unsigned int event_id = 0;
DECLARE_WAITQUEUE (wait, current);
/* Код, необходимый для того, чтобы наш код стал потоком ядра
* без каких-либо ресурсов, присущих пользовательским процессам */
daemonize (”mykthread”);
reparent_to_init (); /* Для ядер ветки 2.4 */
/* Запросить доставку сигнала SIGKILL */
allow_signal (SIGKILL);
/* Поток спит в очереди ожидания, пока он не будет разбужен
* той частью ядра, которая непосредственно имеет дело с интересующей
* нас структурой данных */
add_wait_queue (&myevent_waitqueue, &wait);
for (;;) {
/* Освободить процессор, пока не наступит ожидаемое событие */
set_current_stat (TASK_INTERRUPTIBLE);
schedule ();
/* Выход, если получен сигнал SIGKILL */
if (signal_pending (current)) break;
/* Сюда переходит управление, когда поток разбужен */
read_lock (&myevent_lock); /* Начало критической секции */
if (myevent_id) { /* Защита от "хвостовых" (повторных) пробуждений */
event_id = myevent_id;
read_unlock (&myevent_lock); /* Здесь заканчивается критическая секция */
/* Вызвать зарегистрированное пользовательское приложение-хелпер
* передав ему через переменные окружения необходимую информацию */
run_umode_handler (event_id); /* См. далее */
} else {
read_unlock (&myevent_lock);
}
}
set_current_state (TASK_RUNNING);
remove_wait_queue (&myevent_waitqueue, &wait);
return 0;
}</span></pre>
<br />
<div align="justify">
Если Вы откомпилируете приведённый код в составе ядра, то при загрузке с новым ядром по команде ps сможете увидеть свой новый поток, который выполняется, как потомок init:<span style="color: #000099; font-family: courier new;"></span></div>
<pre><span style="color: #000099; font-family: courier new;">$ ps –ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 21:56 ? 00:00:00 init [3]
root 2 1 0 22:36 ? 00:00:00 [ksoftirqd/0]
…
root 111 1 0 21:56 ? 00:00:00 [mykthread]
…</span></pre>
<br />
<div align="justify">
Прежде чем снова углубляться в детали реализации потоков ядра, взглянем на код ниже:<span style="color: #000099; font-family: courier new;"></span></div>
<pre><span style="color: #000099; font-family: courier new;">/* Выполняется теми частями ядра, которые владеют необходимыми данными, которые мы мониторим */
/* … */
if (my_key_datastructure looks troubled) {
write_lock (&myevent_lock);
/* Записываем наблюдаемую структуру */
myevent_id = datastructure_id;
write_unlock (&myevent_lock);
/* Разбудить поток mykthread */
wake_up_interruptible (&myevent_waitqueue);
}
/* … */</span></pre>
Полезную работу ядро выполняет используя контекст процесса или контекст прерывания. Причём контекст процесса и контекст прерывания не связаны друг с другом. Чтобы не вносить путаницу, необходимо вероятно пояснить, что когда речь идёт о контексте прерывания, то подразумевается не обязательно сам факт прерывания а всего лишь контекст, в котором ядро выполняет отложенные вызовы. Самый первый кусок кода нашей реализации потока выполняется в контексте потока. В то время как код, приведённый абзацем ранее, использует контекст прерывания для обработки отложенной функции и в равной же степени он может быть использован в контексте процесса. Оба вида контекстов связываются посредством структур данных ядра. myevent_id и myevent_waitqueue в приведённом нами коде используются для связывания контекстов. Доступ к переменной myevent_id управляется спин-блокировкой (spin lock). Потоки ядра выполняются в режиме вытесняющей многозадачности только если при конфигурировании ядра включен параметр CONFIG_PREEMPT. Без включения этого параметра и патча вытесняющей многозадачности в ядрах ветки 2.4 наш поток заморозит всю систему, если он сам не заснёт. Если в первом куске кода, приводимом в начале этой статьи, закомментировать вызов schedule(), то с выключенным параметром CONFIG_PREEMPT ядро также окажется заблокированным.<br />
<div align="justify">
Давайте ещё раз посмотрим на код, который усыпляет наш поток mykthread до наступления события.<span style="color: #000099; font-family: courier new;"></span></div>
<pre><span style="color: #000099; font-family: courier new;"> add_wait_queue (&myevent_waitqueue, &wait);
for (;;) {
/* .. */
set_current_state (TASK_INTERRUPTIBLE);
schedule ();
/* Точка A */
/* .. */
}
set_current_state (TASK_RUNNING);
remove_wait_queue (&myevent_waitqueue, &wait);</span></pre>
В очередях ожидания хранятся записи потоков, которые ожидают наступления события или системного ресурса. Поток в очереди ожидания спит до тех пор, пока он не будет разбужен обработчиком прерывания или другим потоком, который ведёт наблюдение за каким-либо объектом. Постановка в очередь и удаление из неё осуществляются с помощью функций add_wait_queue() и remove_wait_queue() соответственно. Пробуждение поставленного в очередь потока/процесса выполняется функцией wake_up_interruptible(). В приведённом выше кусочке кода set_current_state() необходима для того, чтобы установить состояние потока «выполняется».<br />
Поток ядра (равно, как и обычный процесс) может находиться в одном из следующих состояний:<br />
<ul>
<li>TASK_RUNNING: Процесс выполняется (использует процессор) или находится в очереди выполнения, ожидая выделения процессорного времени.</li>
<li>TASK_INTERRUPTIBLE: Процесс приостановлен до наступления определенного события. Это состояние может быть прервано сигналами. После получения сигнала или возобновления путем явного выполнения "пробуждающего" вызова процесс переходит в состояние TASK_RUNNING.</li>
<li>TASK_UNINTERRUPTIBLE: Данное состояние аналогично TASK_INTERRUPTIBLE, с той лишь разницей, что в нем не происходит обработка сигналов. Прерывание процесса, находящегося в этом состоянии, может быть нежелательным, поскольку ожидание может быть частью некоторой важной задачи. При наступлении ожидаемого события процесс возобновляется путем явного выполнения "пробуждающего" вызова.</li>
<li>TASK_STOPPED: Выполнение процесса остановлено, он не выполняется и не может начать выполняться. Процесс переходит в это состояние по получении таких сигналов, как SIGSTOP, SIGTSTP и т.д. Процесс сможет снова стать исполняемым после получения сигнала SIGCONT.</li>
<li>TASK_TRACED: Процесс находится в этом состоянии при выполнении его мониторинга такими процессами, как, например, отладчики.</li>
<li>EXIT_ZOMBIE: Процесс завершен. Он будет находиться в системе до момента получения родительским процессом статистической информации по его выполнению.</li>
<li>EXIT_DEAD: Конечное состояние (соответствующее своему названию). Процесс переходит в это состояние при его удалении из системы после того, как родительский процесс получит всю статистическую информацию, выполнив системный вызов wait4() или waitpid().</li>
</ul>
Кстати, начиная с версии ядра 2.6.25 введено новое состояние процесса – TASK_KILLABLE если процесс приостановлен с приоритетом, допускающим прерывания, он ведет себя так, как если бы находился в состоянии TASK_UNINTERRUPTIBLE, но вдобавок имеет возможность обрабатывать фатальные сигналы.<br />
<div align="justify">
Вернёмся к нашему потоку. mykthread спит, находясь в очереди ожидания (myevent_waitqueue) и изменяет своё состояние на TASK_INTERRUPTIBLE, давая понять, что он отказывается от ожидания в очереди выполнения и, стало быть, квантов времени, которые ему может выделить планировщик выполнения процессов. Вызов функции schedule() необходим для того, чтобы планировщик выбрал другой процесс из очереди выполнения. Когда другая часть ядра будит mykthread с помощью wake_up_interruptible(), поток помещается обратно в очередь выполнения планировщика, а состояние задачи меняется на TASK_RUNNING, т.о. ситуация гонок отсутствует даже в том случае, когда процесс пробуждается, находясь в состоянии TASK_INTERRUPTIBLE (к слову: механизм пробуждения процесса из очереди ожидания, а точнее, механизм самой очереди ожидания предотвращает ситуацию, известную, как "стадо перед грозой", когда несколько процессов ждут одного события - например, освобождения ресурса. Когда ядро будит их, при эсклюзивном доступе к ресурсу получается, что большинство процессов пробуждаются лишь для того, чтобы поучаствовать в гонке за ресурсом и не солоно хлебавши продолжить спать - таких процессов из чсла разбуженных будет большинство, ведь победитель возможен только один) и происходит вызов функции планирования - schedule(). Поток перемещается также в очередь выполнения, если ему доставлен сигнал SIGKILL. Когда планировщик в порядке очерёдности извлекает поток mykthread из очереди выполнения, выполнение потока продолжается в точке А.</div>
<div align="justify">
Теперь, для полной реализации задуманного, всё, что нам остаётся сделать, это реализовать вспомогательное приложение, которое будет запускаться потоком ядра и, собственно, уведомлять пользователя о событии. Итак, хорошая новость. Для нашего удобства ядро поддерживает механизм вызова пользовательских приложений, если требуется выполнить какие-то определённые действия. Например, если включена функция автоматической подгрузки модулей, то ядро по необходимости динамически подгружает необходимые модули, используя для этой цели пользовательский загрузчик модулей. По умолчанию, загрузчик модулей - /sbin/modprobe, но ничто не мешает нам заменить его на другой, зарегистрировав альтернативный загрузчик с помощью файла /proc/sys/kernel/modprobe. Аналогичным образом ядро уведомляет пользовательское окружение о событиях, связанных с устройствами, поддерживающими горячее подключение (hot-plug). В данном случае вспомогательное приложение по умолчанию - /sbin/hotplug, которое можно заменить, переписав содержимое файла /proc/sys/kernel/hotplug. В приведённом далее коде реализована функция, с помощью которой поток ядра mykthread уведомляет пользовательское окружение о наступлении события. Приложение, которое необходимо вызвать, регистрируется в виртуальной файловой системе /proc системным вызовом sysctl, если конечно при конфигурировании ядра был разрешён интерфейс sysctl (CONFIG_SYSCTL). Для того, чтобы добавить свой sysctl-параметр ядра, необходимо добавить новый элемент в массив kern_table в файле kernel/sysctl.c:<span style="color: #000099; font-family: courier new;"></span></div>
<pre><span style="color: #000099; font-family: courier new;">{
KERN_MYEVENT_HANDLER, "myevent_handler", &myevent_handler, 256,
0644, NULL, &proc_dostring, &sysctl_string
}</span></pre>
<br />
<div align="justify">
Благодаря этому при загрузке в модифицированное ядро на файловой системе /proc появится такой файл: /proc/sys/kernel/myevent_handler.</div>
<div align="justify">
Чтобы зарегистрировать свой обработчик, достаточно в командной строке вполнить следующую команду:
<span style="color: #000099; font-family: courier new;"></span></div>
<pre><span style="color: #000099; font-family: courier new;">$ echo /path/to/kernel-helper > /proc/sys/kernel/myevent_handler</span></pre>
<br />
Т.о., при наступлении ожидаемого события будет выполнен наш /path/to/kernel-helper.<span style="color: #000099; font-family: courier new;"></span><br />
<pre><span style="color: #000099; font-family: courier new;">static void run_umode_handler (int event_id)
{
int i = 0;
char *argv[2], *envp[4], *buffer = NULL;
int value;
argv[i++] = myevent_handler; /* Определено в kernel/sysctl.c */
/* Запись идентификатора структуры */
if (!(buffer = kmalloc (32, GFP_KERNEL))) return;
sprintf (buffer, "TROUBLED_DS=%d", event_id);
/* Если пользовательские обработчики не зарегистрованы, то возврат управления */
if (!argv[0]) return;
argv[i] = 0;
/* Подготовка окружения для /path/to/kernel-helper */
i = 0;
envp[i++] = "HOME=/";
envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
envp[i++] = buffer;
envp[i] = 0;
/* Запустить пользовательское приложение, /path/to/kernel-helper */
value = call_usermodehelper (argv[0], argv, envp, 0);
/* Check return values */
…
kfree (buffer);
}</span></pre>
Информацию о структуре данных ядра, которая по каким-то причинам может вызвать у нас интерес, мы передаём пользовательскому процессу через переменную окружения (TROUBLED_DS). В принципе, вспомогательным приложением может быть даже простой шелл-скрипт, который уведомит пользователя по эл.почте о наступившем событии, при чём информацию о наблюдаемой структуре данных ядра он также сможет спокойно получить из переменной окружения:<span style="color: #000099; font-family: courier new;"><pre>#!/bin/bash
echo Kernel datastructure $TROUBLED_DS
is in trouble | mail –s Alert root</pre>
</span>Функция call_usermodehelper() выполняется в контексте запускаемого процесса и работает с полномочиями пользователя root (root capabilities). В ядрах ветки 2.6 этот механизм реализуется как очередь заданий (work queue).<br />
<div align="justify">
Вот мы и рассмотрели в довольно общих чертах потоки ядра, зачем они нужны, как создаются, работают и используются. Но здесь мы привели лишь отрывочные фрагменты кода и немного теории. Самый лучший способ изучить всё в деталях – обратиться к исходному коду ядра. Для тех, кого всё описанное заинтересовало, упоминаемые в тексте потоки ядра ksoftirqd, pdflush, и khubd реализованы соответственно в kernel/softirq.c, mm/pdflush.c и drivers/usb/core/hub.c.</div>
<div align="justify">
Код функции daemonize() можно найти в файле kernel/exit.c для ядер ветки 2.6 и в файле kernel/sched.c, если речь о версии 2.4. Если Вас интересует механизм вызова пользовательских приложений, хелперов ядра, милости просим обратить внимание на код в файле kernel/kmod.c. Вот, пожалуй, и всё. Надеюсь, было интересно и, главное, полезно :)</div>
<h4>
Ссылки.</h4>
[1] Материалы из статьи Sreekrishnan Venkateswaran на <a href="http://www.linux-mag.com/">http://www.linux-mag.com/</a> (увы, не помню точно адрес статьи);<br />[2] "Ядро LINUX", Д.Бовет, М.Чезати, 3-е изд.;<br />[3] Исходные коды ядра Linux (2.6.25-33), как всегда.red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-4778193684325227472010-02-13T19:01:00.000-08:002011-10-17T22:00:33.362-07:00Когда память работает на Вас: использование ramfs и tmpfs<p align="justify">Если производительность операций дискового ввода/вывода не соответствует Вашим нуждам, то наиболее дешёвое и наименее затратное по времени решение - это разместить очень часто используемые файлы в оперативной памяти (RAM). Чтение и запись в память значительно быстрее, чем соответствующие операции на дисковой файловой системе. Операции дискового ввода/вывода, как те, что используются при работе с базами данных, позволяют улучшить производительность, если перенести данные в файловую систему, находящуюся в оперативной памяти.</p><p align="justify">Почему именно оперативная память? Оперативная память быстра. Она работает на скоростях доступа порядка наносекунд. Оперативная память (RAM) не вращается. Дисковые накопители содержат механизмы, которые находятся в движении. Это означает, что операции чтения и записи, требующие поиска, значительно медленнее нежели работа с RAM. Для примера, память DDR3 позволяет перемещать данные со скоростью выше 10ГБ/с. Даже самый быстрый жёсткий диск фирмы Hitachi - UltraStar, работающий на скорости 15000 об/м работает с данными в среднем на скоростях от 119МБ/с до 198МБ/с, а пиковая скорость - максимум 600МБ/с. RAM имеет большее среднее время безотказной работы. Так как RAM не содержит механических частей, то её показатель наработки на отказ гораздо лучше, чем у дисковых накопителей.</p><p align="justify">В качестве файловых систем, работающих в RAM, в Вашем распоряжении есть tmpfs и ramfs. Давайте посмотрим, как можно настроить файловую систему в RAM и попутно избежать некоторых общих проблем при использовании RAM-ФС.</p><h4>ramfs</h4><p align="justify">tmpfs и ramfs работают по-разному. ramfs может использовать только системную память, данные о ней не отображаются по команде df -h, она также не имеет ограничений по размеру и не выдаёт сообщений об ошибках при установке опциональных ограничений (<i>т.е., ограничение размера файловой системы, задаваемое при монтировании, как показано ниже</i> - перев.). То обстоятельство, что ramfs не имеет ограничений по размеру и Вы никогда не увидите предупреждающих сообщений при установке опциональных ограничений может показаться противоречивым, но на самом деле это не так. Вы можете ограничить размер файловой системы ramfs, но Вы не получите предупреждения при превышении этого ограничения, равно как сама система не будет делать ничего, чтобы предотвратить превышение ограничения.</p><p align="justify">Для примера синтаксис команды монтирования новой файловой системы таков:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># mount -t fs_type device mount_dir</pre></span></p><p align="justify">Синтаксис команды mount для настройки RAM-ФС размером в 200МБ для БД в каталоге /opt/data будет таким:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># mount -t ramfs -o size=200m ramfs /opt/data</pre></span></p><p align="justify">Как говорилось ранее, эта ФС не будет отображена по команде df -h. Единственный способ обнаружить её - команда mount.<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># mount
/dev/mapper/VolGroup00-LogVol00 on / type ext3 (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
/dev/sda1 on /boot type ext3 (rw)
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw)
ramfs on /opt/data type ramfs (rw,size=200m)</pre></span></p><p align="justify">Если Вы попытаетесь записать на подмонтированную таким образом ФС более 200МБ данных, запись продолжится без каких-либо предупреждений со стороны системы. Предел в 200МБ является избыточным и никак не влияет на реальный размер ФС или на то, сколько данных Вы можете на неё записать. Это является главным недостатком ramfs.</p><p align="justify">Системные администраторы слишком занятые работой, могут захламлять ФС различными файлами - после установки патчей, ПО, или после продолжительного тестирования системы. Для преодоления этой проблемы ramfs является эффективным решением. Все файлы, в длительном хранении которых на дисковом накопителе нет необходимости, могут размещаться на временной ФС в памяти (ramfs). После размонтирония всё содержимое RAM-ФС утрачивается, а все ненужные файлы т.о. разом исчезают из системы, не засоряя её.</p><p align="justify">Небольшое замечание по поводу ramfs: хоть формально при монтировании ramfs Вы можете указать размер файловой системы, Вам необходимо следить за её заполненностью. И вот почему: размер ramfs увеличивается динамически и система не предотвратит переполнение ФС, как было сказано. Допустим, у Вас 2Гб оперативной памяти, у Вас на /tmp/ram подмонтирована ramfs на 1Гб. Когда данные на Вашей ramfs перевалят за 1Гб, Вы всё ещё сможете дописывать что-то новое в /tmp/ram. Система будет молча выполнять всё, что Вы ей скажете. Однако, когда объём данных превысит объём физической памяти (2Гб в нашем случае), система может зависнуть, т.к. в RAM больше физически нет места для хранения данных. Имейте это ввиду и считайте, что Вас предупредили.</p><h4>tmpfs</h4><p align="justify">Для операций с реальными данными наиболее предпочтительным выбором является tmpfs, нежели ramfs. В отличие от последней, tmpfs имеет фиксированный размер, данные, хранимые на ней, могут размещаться как в системной памяти, так и в разделе подкачки. При превышении размера ФС система выдаёт сообщения об ошибке превышения размера ФС. Синтаксис монтирования tmpfs похож на уже виденный нами:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># mount -t tmpfs -o size=200mb tmpfs /opt/data</pre></span></p><p align="justify">df -h показывает сведения о подмонтированной tmpfs так же, как о любой другой реальной ФС:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VolGroup00-LogVol00
360G 225G 117G 66% /
/dev/sda1 99M 25M 70M 27% /boot
tmpfs 200M 0 200M 0% /opt/data</pre></span></p><p>mount показывает следующую информацию:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># mount
/dev/mapper/VolGroup00-LogVol00 on / type ext3 (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
/dev/sda1 on /boot type ext3 (rw)
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw)
tmpfs on /opt/data type tmpfs (rw,size=200m)</pre></span></p><p align="justify">Когда объём записываемых на ФС данных превышает размер самой tmpfs, система выдаёт сообщение "No space left on device", предупреждая Вас, что ФС заполнена. tmpfs во всём схожа с обычными дисковыми ФС, за исключением того, что она непостоянна. Вы можете добавить в /etc/fstab соответствующую строку, чтобы tmpfs монтировалась после каждой перезагрузки.</p><p align="justify">Следует иметь ввиду, что ramfs и tmpfs - непостоянные ФС, которые находятся в памяти. Это значит, что при крахе системы, перезагрузке или останове - не важно по какой причине, все данные, хранящиеся на RAM-ФС, будут безвозвратно утеряны. Поэтому используя tmpfs Вам следует позаботиться о том, чтобы периодически делать дампы данных с tmpfs на постоянный носитель. Помните пословицу: "Безопасный, быстрый, дешёвый; выбирай любые два". Использование RAM-ФС - это быстрое и недорогое решение, но небезопасное.</p><p align="justify">Оригинал статьи на английском <a href="http://www.linux-mag.com/cache/7689/1.html">здесь</a>.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-39699324235788317212009-05-16T23:31:00.000-07:002009-05-17T00:03:39.398-07:00Связные списки в ядре Linux<h4>Введение:</h4>
<p>ядро linux написано преимущественно на языке С. В отличие от многих других языков программирования С не имеет хороших встроенных средств для работы с коллекциями структурированных данных. Такие средства отсутствуют и в стандартной библиотеке С. Т.о., Вы, вероятно будете рады узнать, что можно позаимствовать хорошую реализацию циклического двусвязного списка, написанную на языке С из кода ядра linux.</p><p>Ясная, лёгкая в использовании реализация циклического двусвязного списка на С находится в файле include/linux/list.h. Код этой реализации эффективен и переносим - в противном случае он не был бы в ядре. Если при программировании ядра необходим список для работы с множеством данных любой структуры, то используют двусвязные списки ядра linux. Путём минимальной правки (удаление аппаратно-зависимого ускорения предвыборки записей списка) списки могут быть адаптированы к использованию в приложениях пользовательского режима. <a href="http://isis.poly.edu/kulesh/stuff/src/klist/list.h">Здесь</a> находится версия файла, пригодная для такого использования.</p><p>Преимущества использования таких списков следующие:<br/><br/><ol>
<li><strong>Независимость от типа:</strong><br/>
Вы можете использовать любую структуру данных, какую заблагорассудится.
</li>
<li><strong>Переносимость:</strong><br/>
Хотя я не пробовал использовать списки на всех платформах, но можно предположить, что данная реализация весьма переносима. В противногм случае она бы не попала в дерево исходного кода ядра.
</li>
<li><strong>Простота использования:</strong><br/>
В силу независимости списков от типа данных записей для инициализации, доступа к элементам списка, прохождения по списку используются одни и те же функции.
</li>
<li><strong>Простота восприятия:</strong><br/>
Макросы и подставляемые (inline) функции делают код очень элегантным и простым для понимания.
</li>
<li><strong>Экономия времени:</strong><br/>
Вам не нужно изобретать колесо. Использование списков позволяет существенно сэкономить время на отладку и повторное создание списков для каждой структуры данных, которая используется в программе.
</li></ol>
Реализация списков в ядре linux отличается от той, которую Вы могли видеть ранее. Обычно, данные содержатся в элементах связанного списка:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
struct my_list {
void *myitem;
struct my_list *next;
struct my_list *prev;
};</pre></span>
Реализации списков в ядре создаёт иллюзию, что это данные содержат в себе список! Например, если у Вас есть список данных со структурой struct my_cool_list, то список будет выглядеть следующим образом:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
struct my_cool_list {
struct list_head list; /* kernel's list structure */
int my_cool_data;
void* my_cool_void;
};</pre></span>
Следует отметить следующее:
<ol>
<li>Список содержится в структурах, которые необходимо связать.</li>
<li>Структуру struct list_head <em><b>можно</b></em> поместить везде в объявлении Вашей структуры.</li>
<li>struct list_head <em><b>может</b></em> иметь любое имя.</li>
<li>У вас в Вашей структуре <em><b>может</b></em> быть несколько полей типа struct list_head!</li>
</ol>
Например, объявление, приведённое ниже также правильно:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
struct todo_tasks{
char *task_name;
unsigned int name_len;
short int status;
int sub_tasks;
int subtasks_completed;
struct list_head completed_subtasks; /* структура списка */
int subtasks_waiting;
struct list_head waiting_subtasks; /* ещё один список таких же или других элементов! */
struct list_head todo_list; /* список дел - todo_tasks */
};</pre></span>
Вот несколько примеров использования списокв в самом ядре:
<ul>
<li><a href="http://lxr.linux.no/source/include/linux/fs.h?v=2.6.10#L362">include/linux/fs.h:362</a></li>
<li><a href="http://lxr.linux.no/source/include/linux/fs.h?v=2.6.10#L429">include/linux/fs.h:429</a></li>
</ul>
Раз уж мы вплотную подошли к спискам, то посмотрим на объявление типа struct list_head в include/linux/list.h:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
struct list_head{
struct list_head *next;
struct list_head *prev;
};</pre></span>
Пожалуй, самое время перейти к деталям. Для начала, давайте посмотрим, как можно использовать этот тип в нашей программе, а затем рассмотрим, как эта структура работает.<p>
<h4>Использование списка:</h4>
<p>Думаю, лучший способ ближе познакомиться с функциями для работы со списками, это посмотреть на содержимое файла исходного кода. Код хорошо откомментирован и его понимание не должно вызвать трудностей.</br></br>
Ниже приведён пример создания, добавления, удаления и прохождения списка.
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
struct kool_list{
int to;
struct list_head list;
int from;
};
int main(int argc, char **argv)
{
struct kool_list *tmp;
struct list_head *pos, *q;
unsigned int i;
struct kool_list mylist;
INIT_LIST_HEAD(&mylist.list);
/* вместо строки выше можно было бы использовать макрос
* LIST_HEAD(mylist) при объявлении списка; данный макрос объявляет
* переменную типа struct list_head с указанным именем и инициализирует её.
*/
/* добавление элементов в mylist */
for (i = 5; i != 0; --i) {
tmp = (struct kool_list *)malloc (sizeof (struct kool_list));
/* INIT_LIST_HEAD(&tmp->list);
*
* здесь производится инициализация поля list динамически созданной структуры типа struct
* kool_list. Эту инициализацию можно пропустить, если следуим будет вызов add_list() или
* любая другая функция подобного рода, которая выполняет инициализацию полей next, prev
*/
printf ("enter to and from:");
scanf ("%d %d", &tmp->to, &tmp->from);
/* добавить новый элемент 'tmp' в список элементов mylist */
list_add(&(tmp->list), &(mylist.list));
/* можно также использовать list_add_tail(), которая добавляет новые элементы
* в хвост списка
*/
}
printf ("\n");
/* теперь у нас есть циклически связанный список элементов типа struct kool_list.
* теперь распечатаем весь список
*/
/* list_for_each() - это макрос, реализующий цикл прохождения по элементам списка.
* первый аргумент макроса используется как счётчик, или иными словами в цикле он
* используется для указания на поле типа list_head текущего элемента списка.
* второй аргумент - указатель на список. Макрос не манипулирует этим указателем.
*/
printf ("traversing the list using list_for_each()\n");
list_for_each(pos, &mylist.list) {
/* в этой строке: pos->next указывает на поле 'list' следующего элемента списка, а
* pos->prev - на поле 'list' предыдущего элемента. Здесь элемент - это запись типа
* struct kool_list. Но нам нужен сам элемент, а не его поле 'list'! list_entry() позволяет
* получить указатель на сам элемент. Как это делается, см. ниже в части "Как
* это работает?".
*/
tmp = list_entry (pos, struct kool_list, list);
/* в качестве аргументов макрос принимает указатель на struct list_head, в котором
* хранится текущая позиция списка, второй аргумент - тип пользовательской структуры,
* членом которой является поле, на которое указывает, первый аргумент и третий
* аргумент - имя этого поля (член типа struct list_head в пользовательской структуре).
* Макрос возвращает указатель на структуру, членом которой является первый аргумент.
* Или точнее говоря, на который указывает первый аргумент - pos.
* Например, выше list_entry() возвратит указатель на элемент типа struct kool_list,
* частью которого является pos
*/
printf("to = %d from= %d\n", tmp->to, tmp->from);
}
printf ("\n");
/* т.к. наш список цикличен, то его можно обойти и в обратном порядке.
* всё, что для этого надо - заменить макрос 'list_for_each' на 'list_for_each_prev',
* а всё остальное останется без изменений.
*
* Также можно использовать list_for_each_entry(), чтобы пройти по элементам списка
* данного типа. Например:
*/
printf ("traversing the list using list_for_each_entry()\n");
list_for_each_entry(tmp, &mylist.list, list)
printf ("to = %d from = %d\n", tmp->to, tmp->from);
/* Как видно, здесь мы не используем макрос list_entry(), чтобы получить указатель
* на элемент списка типа struct kool_list. Этот указатель помещается в переменную tmp
* на "полуавтомате" самим макросом list_for_each_entry() :)
*/
printf ("\n");
/* теперь будем вежливыми и освободим память, захваченную под записи kool_list items.
* т.к. мы будем удалять записи из списка с помощью list_del(), нам нужна защищённая
* версия макроса list_for_each().
* такая версия названа list_for_each_safe(). Этот макрос НЕОБХОДИМО использовать
* для организации цикла, который предусматривает удаление или перемещение записей
* из одного списка в другой.
*/
printf ("deleting the list using list_for_each_safe()\n");
list_for_each_safe(pos, q, &mylist.list){
tmp = list_entry(pos, struct kool_list, list);
printf ("freeing item to = %d from = %d\n", tmp->to, tmp->from);
list_del(pos);
free (tmp);
}
return 0;
}</pre></span></p>
<h4>Как это работает?</h4>
<p>В общем, большая часть реализации довольна тривиальна, но тонко она выполнена. Тонкость заключается в том, чтобы получить адрес целой структуры по адресу члена, являющегося частью этой структуры (т.е., получить указатель на структуру, содержащую в себе элемент типа struct list_head). Этот трюк проделывает макрос list_entry(), как мы увидели ранее. Теперь попытаемся понять, как он это делает.<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
</pre></span>
Если опираться на ранее приведённый пример, то макрос будет развёрнут следующим образом:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
((struct kool_list *)((char *)(pos) - (unsigned long)(&((struct kool_list *)0)->list)))
</pre></span>
Это смущает многих, однако приём достаточно прост и является широко известным (см. вопрос 2.14 - англ.). Используя указатель на поле типа struct list_head в структуре данных, макрос list_entry() просто высчитывает адрес самой структуры. Чтобы выполнить такой расчёт, необходимо выяснить, где в структуре данных находится член типа list_head (смещение list_head). Затем просто вычисляется смещение члена типа list_head по отношению к указателю, который был передан макросу в качестве первого аргумента.</br></br>
Следующий вопрос, как нам вычислить смещение элемента внутри структуры данных? Допустим, у нас есть структура struct foo_bar и нам надо найти смещение элемента boo, который является членом структуры. Сделаем это следующим образом:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
(unsigned long)(&((struct foo_bar *)0)->boo)
</pre></span>
Возьмём нулевой адрес памяти и приведём его к указателю на нашу структуру - в данной случае, к указателю на struct foo_bar. Затем возьмём адрес поля, которое нас интересует. Это и будет смещение данного поля внутри структуры. Так как мы узнали абсолютное положение данного элемента в памяти, мы теперь можем с помощью этого элемента получить указатель на конкретный экземпляр целой структуры, опираясь на некоторые данные (например, аргумент pos в случае с макросом list_entry). Вот и всё. Чтобы лучше понять, что происходит, советую вам погонять <a href="http://isis.poly.edu/kulesh/stuff/src/klist/compute_offset.c">этот код</a>.
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
#include <stdio.h>
#include <stdlib.h>
struct foobar {
unsigned int foo;
char bar;
char boo;
};
int main (int argc, char** argv)
{
struct foobar tmp;
printf ("address of &tmp is= %p\n\n", &tmp);
printf ("address of tmp->foo= %p \t offset of tmp->foo= %lu\n", &tmp.foo,
(unsigned long) &((struct foobar *)0)->foo);
printf ("address of tmp->bar= %p \t offset of tmp->bar= %lu\n", &tmp.bar,
(unsigned long) &((struct foobar *)0)->bar);
printf ("address of tmp->boo= %p \t offset of tmp->boo= %lu\n\n", &tmp.boo,
(unsigned long) &((struct foobar *)0)->boo);
printf ("computed address of &tmp using:\n");
printf ("\taddress and offset of tmp->foo= %p\n",
(struct foobar *) (((char *) &tmp.foo) - ((unsigned long) &((struct foobar *)0)->foo)));
printf ("\taddress and offset of tmp->bar= %p\n",
(struct foobar *) (((char *) &tmp.bar) - ((unsigned long) &((struct foobar *)0)->bar)));
printf ("\taddress and offset of tmp->boo= %p\n",
(struct foobar *) (((char *) &tmp.boo) - ((unsigned long) &((struct foobar *)0)->boo)));
return 0;
}</pre></span>
Вывод приведённого куска кода:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
address of &tmp is = 0xbfffed00
address of tmp->foo = 0xbfffed00 offset of tmp->foo= 0
address of tmp->bar = 0xbfffed04 offset of tmp->bar= 4
address of tmp->boo = 0xbfffed05 offset of tmp->boo= 5
computed address of &tmp using:
address and offset of tmp->foo = 0xbfffed00
address and offset of tmp->bar = 0xbfffed00
address and offset of tmp->boo = 0xbfffed00
</pre></span>
</p>
<h4>См. также:</h4>
<ul>
<li>Код хэш-таблиц, организованных в виде списков.</li>
<li>Ещё код <a href="http://isis.poly.edu/kulesh/stuff/src/">/sutff/src/</a></li>
</ul>
<p>Оригинал статьи на английском лежит <a href="http://isis.poly.edu/kulesh/stuff/src/klist/">здесь</a>.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-19383180174386132612008-03-18T11:19:00.000-07:002011-01-22T13:07:15.627-08:00Linux - syscalls. Системные вызовы в Linux.<div align="right"><i>О многом - молвил Морж,- пришла пора поговорить.<br>Л. Кэролл (Цитата по книге Б. Страустрапа)</i></div><br><br><div align="right"><i>Как бы у нас ни сложилось дальше, я благодарен тебе за то прекрасное время,<br>что у нас было и что навсегда останется со мною.<br>В появлении этой статьи есть и твоя заслуга...</i></div><h4>Вместо введения.</h4><p>По теме внутреннего устройства ядра Linux в общем, его различных подсистемах и о системных вызовах в частности, было писано и переписано уже порядком. Наверно, каждый уважающий себя автор должен хоть раз об этом написать, подобно тому, как каждый уважающий себя программист обязательно должен написать свой собственный файл-менеджер :) Хотя я и не являюсь профессиональным IT-райтером, да и вообще, делаю свои пометки исключительно для своей пользы в первую очередь, чтобы не забыть изученное слишком быстро. Но, если уж кому-то пригодятся мои путевые заметки, конечно, буду только рад. Ну и вообще, кашу маслом не испортишь, поэтому, может даже мне и удастся написать или описать что-то такое, о чём никто не удосужился упомянуть.</p><h4>Теория. Что такое системные вызовы?</h4>
<p>Когда непосвящённым объясняют, что такое ПО (или ОС), то говорят обычно следующее: компьютер сам по себе - это железяка, а вот софт - это то, благодаря чему от этой железяки можно получить какую-то пользу. Грубовато, конечно, но в целом, в чём-то верно. Так же я сказал бы наверно об ОС и системных вызовах. На самом деле, в разных ОС системные вызовы могут быть реализованы по-разному, может розниться число этих самых вызовов, но так или иначе, в том или ином виде механизм системных вызовов есть в любой ОС. Каждый день пользователь явно или не явно работает с файлами. Он конечно может явно открыть файл для редактирования в своём любимом MS Word'е или Notepad'е, а может и просто запустить игрушку, исполняемый образ которой, к слову, тоже хранится в файле, который, в свою очередь, должен открыть и прочитать загрузчик исполняемых файлов. В свою очередь игрушка также может открывать и читать десятки файлов в процессе своей работы. Естественно, файлы можно не только читать, но и писать (не всегда, правда, но здесь речь не о разделении прав и дискретном доступе :) ). Всем этим заведует ядро (в микроядерных ОС ситуация может отличаться, но мы сейчас будем ненавязчиво клониться к объекту нашего обсуждения - Linux, поэтому проигнорируем этот момент). Само по себе порождение нового процесса - это также услуга, предоставляемая ядром ОС. Всё это замечательно, как и то, что современные процессоры работают на частотах гигагерцевых диапазонов и состоят из многих миллионов транзисторов, но что дальше? Да то, что если бы небыло некого механизма, с помощью которого пользовательские приложения могли выполнять некоторые достаточно обыденные и, в то же время, нужные вещи (<i>на самом деле, эти тривиальные действия при любом раскладе выполняются не пользовательским приложением, а ядром ОС - авт.</i>), то ОС была просто вещью в себе - абсолютно бесполезной или же напротив, каждое пользовательское приложение само по себе должно было бы стать операционной системой, чтобы самостоятельно обслуживать все свои нужды. Мило, не правда ли?</p><p>Таким образом, мы подошли к определению системного вызова в первой аппроксимации: системный вызов - это некая услуга, которую ядро ОС оказывает пользовательскому приложению по запросу последнего. Такой услугой может быть уже упомянутое открытие файла, его создание, чтение, запись, создание нового процесса, получение идентификатора процесса (pid), монтирование файловой системы, останов системы, наконец. В реальной жизни системных вызовов гораздо больше, нежели здесь перечислено.</p><p>Как выглядит системный вызов и что из себя представляет? Ну, из того, что было сказано выше, становится ясно, что системный вызов - это подпрограмма ядра, имеющая соответственный вид. Те, кто имел опыт программирования под Win9х/DOS, наверняка помнят прерывание int 0x21 со всеми (или хотя бы некоторыми) его многочисленными функциями. Однако, есть одна небольшая особенность, касающаяся всех системных вызовов Unix. По соглашению функция, реализующая системный вызов, может принимать N аргументов или не принимать их вовсе, но так или иначе, функция должна возвращать значение типа int. Любое неотрицательное значение трактуется, как успешное выполнение функции системного вызова, а стало быть и самого системного вызова. Значение меньше нуля является признаком ошибки и одновременно содержит код ошибки (коды ошибок определяются в заголовках include/asm-generic/errno-base.h и include/asm-generic/errno.h). В Linux шлюзом для системных вызовов до недавнего времени было прерывание int 0x80, в то время, как в Windows (вплоть до версии XP Service Pack 2, если не ошибаюсь) таким шлюзом является прерывание 0x2e. Опять же, в ядре Linux, до недавнего времени все системные вызовы обрабатывались функцией system_call(). Однако, как выяснилось позднее, классический механизм обрабатки системных вызовов через шлюз 0x80 приводит к существенному падению производительности на процессорах Intel Pentium 4. Поэтому на смену классическому механизму пришёл метод виртуальных динамических разделяемых объектов (DSO - динамический разделяемый объектный файл. Не ручаюсь за правильный перевод, но DSO, это то, что пользователям Windows известно под названием DLL - динамически загружаемая и компонуемая библиотека) - VDSO. В чём отличие нового метода от классического? Для начала разберёмся с классическим методом, работающим через гейт 0x80.</p><h4>Классический механизм обслуживания системных вызовов в Linux.</h4><h5>Прерывания в архитектуре х86.</h5>
<p>Как было сказано выше, ранее для обслуживания запросов пользовательских приложений использовался шлюз 0x80 (int 0x80). Работа системы на базе архитектуры IA-32 управляется прерываниями (строго говоря, это касается вообще всех систем на базе x86). Когда происходит некое событие (новый тик таймера, какая-либо активность на некотором устройстве, ошибки - деление на ноль и пр.), генерируется прерывание. Прерывание (interrupt) названо так, потому что оно, как правило, прерывает нормальный ход выполнения кода. Прерывания принято подразделять на аппаратные и программные (hardware and software interrupts). Аппаратные прерывания - это прерывания, которые генерируются системными и периферическими устройствами. При возникновении необходимости какого-то устройства привлечь к себе внимание ядра ОС оно (устройство) генерирует сигнал на своей линии запроса прерывания (IRQ - Interrupt ReQuest line). Это приводит к тому, что на определённых входах процессора формируется соответствующий сигнал, на основе которого процессор и принимает решение прервать выполнение потока инструкций и передать управление на обработчик прерывания, который уже выясняет, что произошло и что необходимо сделать. Аппаратные прерывания асинхронны по своей природе. Это значит, что прерывание может возникнуть в любое время. Кроме периферийных устройств, сам процессор может вырабатывать прерывания (или, точнее, аппаратные исключения - Hardware Exceptions - например, уже упомянутое деление на ноль). Делается это с тем, чтобы уведомить ОС о возникновении нештатной ситуации, чтобы ОС могла предпринять некое действие в ответ на возникновение такой ситуации. После обработки прерывания процессор возвращается к выполнению прерванной программы. Прерывание может быть инициировано пользовательским приложением. Такое прерывание называют программным. Программные прерывания, в отличие от аппаратных, синхронны. Т.е., при вызове прерывания, вызвавший его код приостанавливается, пока прерывание не будет обслужено. При выходе из обработчика прерывания происходит возврат по дальнему адресу, сохранённому ранее (при вызове прерывания) в стеке, на следующую инструкцию после инструкции вызова прерывания (int). Обработчик прерывания - это резидентный (постоянно находящийся в памяти) участок кода. Как правило, это небольшая программа. Хотя, если мы будем говорить о ядре Linux, то там обработчик прерывания не всегда такой уж маленький. Обработчик прерывания определяется вектором. Вектор - это ни что иное, как адрес (сегмент и смещение) начала кода, который должен обрабатывать прерывания с данным индексом. Работа с прерываниями существенно отличается в реальном (Real Mode) и защищённом (Protected Mode) режиме работы процессора (напомню, что здесь и далее мы подразумеваем процессоры Intel и совместимые с ними). В реальном (незащищённом) режиме работы процессора обработчики прерываний определяются их векторами, которые хранятся всегда в начале памяти выборка нужного адреса из таблицы векторов происходит по индексу, который также является номером прерывания. Перезаписав вектор с определённым индексом можно назначить прерыванию свой собственный обработчик.</p><p>В защищённом режиме обработчики прерываний (шлюзы, гейты или вентили) больше не определяются с помощью таблицы векторов. Вместо этой таблицы используется таблица вентилей или, правильнее, таблица прерываний - IDT (Interrupt Descriptors Table). Эта таблица формируется ядром, а её адрес хранится в регистре процессора idtr. Данный регистр недоступен напрямую. Работа с ним возможна только при помощи инструкций lidt/sidt. Первая из них (lidt) загружает в регистр idtr значение, указанное в операнде и являющееся базовым адресом таблицы дексрипторов прерываний, вторая (sidt) - сохраняет адрес таблицы, находящийся в idtr, в указанный операнд. Так же, как происходит выборка информации о сегменте из таблицы дескрипторов по селектору, происходит и выборка дескриптора сегмента, обслуживающего прерывание в защищённом режиме. Защита памяти поддерживается процессорами Intel начиная с CPU i80286 (не совсем в том виде, в каком она представлена сейчас, хотя бы потому, что 286 был 16-разрядным процессором - поэтому Linux не может работать на этих процессорах) и i80386, а посему процессор самостоятельно производит все необходимые выборки и, стало быть, сильно углубляться во все тонкости защищённого режима (а именно в защищённом режиме работает Linux) мы не будем. К сожалению, ни время, ни возможности не позволяют нам остановиться надолго на механизме обработки прерываний в защищённом режиме. Да это и не было целью при написании данной статьи. Все сведения, приводимые здесь касательно работы процессоров семейства х86 довольно поверхностны и приводятся лишь для того, чтобы помочь немного лучше понять механизм работы системных вызовов ядра. Кое-что можно узнать непосредственно из кода ядра, хотя, для полного понимания происходящего, желательно всё же ознакомиться с принципами работы защищённого режима. Участок кода, который заполняет начальными значениями (но не устанавливает!) IDT, находится в arch/i386/kernel/head.S:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>/*
* setup_idt
*
* sets up a idt with 256 entries pointing to
* ignore_int, interrupt gates. It doesn't actually load
* idt - that can be done only after paging has been enabled
* and the kernel moved to PAGE_OFFSET. Interrupts
* are enabled elsewhere, when we can be relatively
* sure everything is ok.
*
* Warning: %esi is live across this function.
*/
1.setup_idt:
2. lea ignore_int,%edx
3. movl $(__KERNEL_CS << 16),%eax
4. movw %dx,%ax /* selector = 0x0010 = cs */
5. movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
6. lea idt_table,%edi
7. mov $256,%ecx
8.rp_sidt:
9. movl %eax,(%edi)
10. movl %edx,4(%edi)
11. addl $8,%edi
12. dec %ecx
13. jne rp_sidt
14..macro set_early_handler handler,trapno
15. lea \handler,%edx
16. movl $(__KERNEL_CS << 16),%eax
17. movw %dx,%ax
18. movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
19. lea idt_table,%edi
20. movl %eax,8*\trapno(%edi)
21. movl %edx,8*\trapno+4(%edi)
22..endm
23. set_early_handler handler=early_divide_err,trapno=0
24. set_early_handler handler=early_illegal_opcode,trapno=6
25. set_early_handler handler=early_protection_fault,trapno=13
26. set_early_handler handler=early_page_fault,trapno=14
28. ret</pre></span>Несколько замечаний по коду: приведённый код написан на разновидности ассемблера AT&T, поэтому Ваше знание ассемблера в его привычной интеловской нотации может только сбить с толку. Самое основное отличие в порядке операндов. Если для интеловской нотации определён порядок - "аккумулятор" < "источник", то для ассемблера AT&T порядок прямой. Регистры процессора, как правило, должны иметь префикс "%", непосредственные значения (константы) префиксируются символом доллара "$". Синтаксис AT&T традиционно используется в Un*x-системах.</p><p>В приведённом примере в строках 2-4 устанавливается адрес обработчика всех прерываний по умолчанию. Обработчиком по умолчанию является функция ignore_int, которая ничего не делает. Наличие такой заглушки необходимо для корректной обработки всех прерываний на данном этапе, так как других ещё просто нет (правда, ловушки (traps) устанавливаются немного ниже по коду - о ловушках см. Intel Architecture Manual Reference или что-то подобное, здесь мы не будем касаться ловушек). В строке 5 устанавливается тип вентиля. В строке 6 мы загружаем в индексный регистр адрес нашей таблицы IDT. Таблица должна содержать 255 записей, по 8 байт каждая. В строках 8-13 мы заполняем всю таблицу одними и теми же значениями, установленными ранее в регистрах eax и edx - т.е., это вентиль прерывания, ссылающийся на обработчик ignore_int. Чуть ниже мы определяем макрос для установки ловушек (traps) - строки 14-22. В строках 23-26 используя вышеопределённый макрос мы устанавливаем ловушки для следующих исключений: early_divide_err - деление на ноль (0), early_illegal_opcode - неизвестная инструкция процессора (6), early_protection_fault - сбой защиты памяти (13), early_page_fault - отказ страничной трансляции (14). В скобках приведены номера "прерываний", генерируемые при возникновении соответствующей нештатной ситуации. Перед проверкой типа процессора в arch/i386/kernel/head.S таблица IDT устанавливается вызовом setup_idt:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>/*
* start system 32-bit setup. We need to re-do some of the things done
* in 16-bit mode for the "real" operations.
*/
1. call setup_idt
...
2. call check_x87
3. lgdt early_gdt_descr
4. lidt idt_descr</pre></span>После выяснения типа (со)процессора и проведения всех подготовительных действий в строках 3 и 4 мы загружаем таблицы GDT и IDT, которые будут использоваться на самых первых порах работы ядра.</p><h4>Системные вызовы и int 0x80.</h4><p>От прерываний вернёмся обратно к системным вызовам. Итак, что необходимо, чтобы обслужить процесс, который запрашивает какую-то услугу? Для начала, необходимо перейти из кольца 3 (уровень привелегий CPL=3) на наиболее привелигированный уровень 0 (Ring 0, CPL=0), т.к. код ядра расположен в сегменте с наивысшими привелегиями. Кроме того, необходим код обработчика, который обслужит процесс. Именно для этого и используется шлюз 0x80. Хотя системных вызовов довольно много, для всех них используется единая точка входа - int 0x80. Сам обработчик устанавливается при вызове функции arch/i386/kernel/traps.c::trap_init():<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>void __init trap_init(void)
{
...
set_system_gate(SYSCALL_VECTOR,&system_call);
...
}</pre></span>Нас в trap_init() больше всего интересует эта строка. В этом же файле выше можно посмотреть на код функции set_system_gate():
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>static void __init set_system_gate(unsigned int n, void *addr)
{
_set_gate(n, DESCTYPE_TRAP | DESCTYPE_DPL3, addr, __KERNEL_CS);
}</pre></span>Здесь видно, что вентиль для прерывания 0х80 (а именно это значение определено макросом SYSCALL_VECTOR - можете поверить наслово :) ) устанавливается как ловушка (trap) с уровнем привелегий DPL=3 (Ring 3), т.е. это прерывание будет отловлено при вызове из пространства пользователя. Проблема с переходом из Ring 3 в Ring 0 т.о. решена. Функция _set_gate() определена в заголовочном файле include/asm-i386/desc.h . Для особо любопытных ниже приведён код, без пространных объяснений, впрочем:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>static inline void _set_gate(int gate, unsigned int type, void *addr, unsigned short seg)
{
__u32 a, b;
pack_gate(&a, &b, (unsigned long)addr, seg, type, 0);
write_idt_entry(idt_table, gate, a, b);
}</pre></span>Вернёмся к функции trap_init(). Она вызывается из функции start_kernel() в init/main.c . Если посмотреть на код trap_init(), то видно, что эта функция переписывает некоторые значения таблицы IDT заново - обработчики, которые использовались на ранних стадиях инициализации ядра (early_page_fault, early_divide_err, early_illegal_opcode, early_protection_fault), заменяются на те, что будут использоваться уже в процессе работы ядра. Итак, мы практически добрались до сути и уже знаем, что все системные вызовы обрабатываются единообразно - через шлюз int 0x80. В качестве обработчика для int 0x80, как опять же видно из приведённого выше куска кода arch/i386/kernel/traps.c::trap_init(), устанавливается функция system_call().</p><h4>system_call().</h4><p>Код функции system_call() находится в файле arch/i386/kernel/entry.S и выглядит следующим образом:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
# system call handler stub
ENTRY(system_call)
RING0_INT_FRAME # can't unwind into user space anyway
pushl %eax # save orig_eax
CFI_ADJUST_CFA_OFFSET 4
SAVE_ALL
GET_THREAD_INFO(%ebp)
# system call tracing in operation / emulation
/* Note, _TIF_SECCOMP is bit number 8, and so it needs testw and not testb */
testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry
cmpl $(nr_syscalls), %eax
jae syscall_badsys
syscall_call:
call *sys_call_table(,%eax,4)
movl %eax,PT_EAX(%esp) # store the return value
...</pre></span>Код приведён не полностью. Как видно, сперва system_call() настраивает стек для работы в Ring 0, сохраняет значение, переданное ей через eax в стек, сохраняет все регистры также в стек, получает данные о вызывающем потоке и проверяет, не выходит ли переданное значение-номер системного вызова за пределы таблицы системных вызовов и затем, наконец, пользуясь значением, переданным в eax в качестве аргумента, system_call() осуществляет переход на настоящий обработчик системного вывода, исходя из того, на какой элемент таблицы ссылается индекс в eax. Теперь вспомните старую добрую таблицу векторов прерываний из реального режима. Ничего не напоминает? В реальности, конечно, всё несколько сложнее. В частности, системный вызов должен скопировать результаты из стека ядра в стек пользователя, передать код возврата и ещё некоторые вещи. В том случае, когда аргумент, указанный в eax не ссылается на существующий системный вызов (значение выходит за диапазон), происходит переход на метку syscall_badsys. Здесь в стек по смещению, по которому должно находиться значение eax, заносится значение -ENOSYS - системный вызов не реализован. На этом выполнение system_call() завершается.</p><p>Таблица системных вызовов находится в файле arch/i386/kernel/syscall_table.S и имеет достаточно простой вид:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
.long sys_exit
.long sys_fork
.long sys_read
.long sys_write
.long sys_open /* 5 */
.long sys_close
.long sys_waitpid
.long sys_creat
...</pre></span>Иными словами, вся таблица являет собой ничто иное, как массив адресов функций, расположенных в порядке следования номеров системных вызовов, которые эти функции обслуживают. Таблица - обычный массив двойных машинных слов (или 32-разрядных слов - кому как больше нравится). Код части функций, обслуживающих системные вызовы, находится в платформно-зависимой части - arch/i386/kernel/sys_i386.c, а часть, не зависящая от платформы - в kernel/sys.c .</p><p>Вот так обстоит дело с системными вызовами и вентилем 0x80.</p><h4>Новый механизм обработки системных вызовов в Linux. sysenter/sysexit.</h4><p>Как упоминалось, достаточно быстро выяснилось, что использование традиционного способа обработки системных вызовов на основе гейта 0х80 пиводит к потере производительности на процессорах Intel Pentium 4. Поэтому Линус Торвальдс реализовал в ядре новый механизм, основанный на инструкциях sysenter/sysexit и призванный повысить производительность ядра на машинах, оснащённых процессором Pentium II и выше (именно с Pentium II+ процессоры Intel поддерживают упомянутые инструкции sysenter/sysexit). В чём суть нового механизма? Как ни странно, но суть осталась та же. Изменилось исполнение. Согласно документации Intel инструкция sysenter является частью механизма "быстрых системных вызовов". В частности, эта инструкция оптимизирована для быстрого перехода с одного уровня привелегий на другой. Если точнее, то она ускоряет переход в кольцо 0 (Ring 0, CPL=0). При этом, операционная система должна подготовить процессор к использовании инструкции sysenter. Такая настройка осуществляется единожды при загрузке и инициализации ядра ОС. При вызове sysenter устанавливает регистры процессора согласно машинно-зависимых регистров, ранее установленных ОС. В частности, устанавливаются сегментный регистр и регистр указателя инструкций - cs:eip, а также сегмент стека и указатель вершины стека - ss, esp. Переход на новый сегмент кода и смещение осуществляется из кольца 3 в 0.</p><p>Инструкция sysexit выполняют обратные действия. Она производит быстрый переход с уровня привелегий 0 на 3-й (CPL=3). При этом регистр сегмента кода устанавливается в 16 + значение сегмента cs, сохранённое в машинно-зависимом регистре процессора. В регистр eip заносится содержимое регистра edx. В ss заносится сумма 24 и значения cs, занесённое ОС ранее в машинно-зависимый регистр процессора при подготовке контекста для работы инструкции sysenter. В esp заносится содержимое регистра ecx. Значения, необходимые для работы инструкций sysenter/sysexit хранятся по следующим адресам:<ol><li>SYSENTER_CS_MSR 0х174 - сегмент кода, куда заносится значение сегмента, в котором находится код обработчика системного вызова.</li><li>SYSENTER_ESP_MSR 0х175 - указатель на вершину стека для обработчика системного вызова.</li><li>SYSENTER_EIP_MSR 0х176 - указатель на смещение внутри сегмента кода. Указывает на начало кода обработчика системных вызовов.</li></ol>Данные адреса ссылаются на модельно-зависимые регистры, которые не имеют имён. Значения записываются в модельно зависимые регистры с помощью инструкции wrmsr, при этом edx:eax должны содержать страшую и младшую части 64-битного машинного слова соответственно, а в ecx должен быть занесён адрес ригистра, в который будет произведена запись. В Linux адреса модельно-зависимых регистров определяются в заголовочном файле include/asm-i368/msr-index.h следующим образом (до версии 2.6.22 как минимум они определялись в заголовочном файле include/asm-i386/msr.h, напомню, что мы рассматриваем механизм системных вызовов на примере ядра Linux 2.6.22):<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#define MSR_IA32_SYSENTER_CS 0x00000174
#define MSR_IA32_SYSENTER_ESP 0x00000175
#define MSR_IA32_SYSENTER_EIP 0x00000176
</pre></span>Код ядра, ответственный за установку модельно-зависимых регистров, находится в файле arch/i386/sysenter.c и выглядит следующим образом:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>1. void enable_sep_cpu(void)
{
2. int cpu = get_cpu();
3. struct tss_struct *tss = &per_cpu(init_tss, cpu);
4. if (!boot_cpu_has(X86_FEATURE_SEP)) {
5. put_cpu();
6. return;
}
7. tss->x86_tss.ss1 = __KERNEL_CS;
8. tss->x86_tss.esp1 = sizeof(struct tss_struct) + (unsigned long) tss;
9. wrmsr(MSR_IA32_SYSENTER_CS, __KERNEL_CS, 0);
10. wrmsr(MSR_IA32_SYSENTER_ESP, tss->x86_tss.esp1, 0);
11. wrmsr(MSR_IA32_SYSENTER_EIP, (unsigned long) sysenter_entry, 0);
12. put_cpu();
}</pre></span>Здесь в переменную tss мы получаем адрес структуры, описывающей сегмент состояния задачи. TSS (Task State Segment) используется для описания контекста задачи и является частью механизма аппаратной поддержки многозадачности для архитектуры x86. Однако, Linux практически не использует аппаратное переключение контекста задач. Согласно документации Intel переключение на другую задачу производится либо путём выполнения инструкции межсегментного перехода (jmp или call), ссылающейся на сегмент TSS, либо на дескриптор вентиля задачи в GDT (LDT). Специальный регистр процессора, невидимый для программиста - TR (Task Register - регистр задачи) содержит селектор дескриптора задачи. При загрузке этого регистра также загружаются программно-невидимые регистры базы и лимита, связанные с TR.</p><p>Несмотря на то, что Linux не использует аппаратное переключение контекстов задач, ядро вынуждено отводить запись TSS для каждого процессора, установленного в системе. Это связано с тем, что когда процессор переключается из пользовательского режима в режим ядра, он извлекает из TSS адрес стека ядра. Кроме того, TSS необходим для управления доступом к портам ввода/вывода. TSS содержит карту прав доступа к портам. На основе этой карты становится возможным осуществлять контроль доступа к портам для каждого процесса, использующего инструкции in/out. Здесь tss->x86_tss.esp1 указывает на стек ядра. __KERNEL_CS естественно указывает на сегмент кода ядра. В качестве смещения-eip указывается адрес функции sysenter_entry().</p><p>Функция sysenter_entry() определена в файле arch/i386/kernel/entry.S и имеет такой вид:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>/* SYSENTER_RETURN points to after the "sysenter" instruction in
the vsyscall page. See vsyscall-sysentry.S, which defines the symbol. */
# sysenter call handler stub
ENTRY(sysenter_entry)
CFI_STARTPROC simple
CFI_SIGNAL_FRAME
CFI_DEF_CFA esp, 0
CFI_REGISTER esp, ebp
movl TSS_sysenter_esp0(%esp),%esp
sysenter_past_esp:
/*
* No need to follow this irqs on/off section: the syscall
* disabled irqs and here we enable it straight after entry:
*/
ENABLE_INTERRUPTS(CLBR_NONE)
pushl $(__USER_DS)
CFI_ADJUST_CFA_OFFSET 4
/*CFI_REL_OFFSET ss, 0*/
pushl %ebp
CFI_ADJUST_CFA_OFFSET 4
CFI_REL_OFFSET esp, 0
pushfl
CFI_ADJUST_CFA_OFFSET 4
pushl $(__USER_CS)
CFI_ADJUST_CFA_OFFSET 4
/*CFI_REL_OFFSET cs, 0*/
/*
* Push current_thread_info()->sysenter_return to the stack.
* A tiny bit of offset fixup is necessary - 4*4 means the 4 words
* pushed above; +8 corresponds to copy_thread's esp0 setting.
*/
pushl (TI_sysenter_return-THREAD_SIZE+8+4*4)(%esp)
CFI_ADJUST_CFA_OFFSET 4
CFI_REL_OFFSET eip, 0
/*
* Load the potential sixth argument from user stack.
* Careful about security.
*/
cmpl $__PAGE_OFFSET-3,%ebp
jae syscall_fault
1: movl (%ebp),%ebp
.section __ex_table,"a"
.align 4
.long 1b,syscall_fault
.previous
pushl %eax
CFI_ADJUST_CFA_OFFSET 4
SAVE_ALL
GET_THREAD_INFO(%ebp)
/* Note, _TIF_SECCOMP is bit number 8, and so it needs testw and not testb */
testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry
cmpl $(nr_syscalls), %eax
jae syscall_badsys
call *sys_call_table(,%eax,4)
movl %eax,PT_EAX(%esp)
DISABLE_INTERRUPTS(CLBR_ANY)
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
testw $_TIF_ALLWORK_MASK, %cx
jne syscall_exit_work
/* if something modifies registers it must also disable sysexit */
movl PT_EIP(%esp), %edx
movl PT_OLDESP(%esp), %ecx
xorl %ebp,%ebp
TRACE_IRQS_ON
1: mov PT_FS(%esp), %fs
ENABLE_INTERRUPTS_SYSEXIT
CFI_ENDPROC
.pushsection .fixup,"ax"
2: movl $0,PT_FS(%esp)
jmp 1b
.section __ex_table,"a"
.align 4
.long 1b,2b
.popsection
ENDPROC(sysenter_entry)</pre></span>Как и в случае с system_call() основная работа выполняется в строке call *sys_call_table(,%eax,4). Здесь вызывается конкретный обработчик системного вызова. Итак, видно, что принципиально изменилось мало. То обстоятельство, что вектор прерывания теперь забит в железо и процессор помогает нам быстрее перейти с одного уровня привелегий на другой меняет лишь некоторые детали исполнения при прежнем содержании. Правда, на этом изменения не заканчиваются. Вспомните, с чего начиналось повествование. В самом начале я упоминал уже о виртуальных разделяемых объектах. Так вот, если раньше реализация системного вызова, скажем, из системной библиотеки libc выглядела, как вызов прерывания (при том, что библиотека брала некоторые функции на себя, чтобы сократить число переключений контекстов), то теперь благодаря VDSO системный вызов может быть сделан практически напрямую, без участия libc. Он и ранее мог быть осуществлён напрямую, опять же, как прерывание. Но теперь вызов можно затребовать, как обычную функцию, экспортируемую из динамически компонуемой библиотеки (DSO). При загрузке ядро определяет, какой механизм должен и может быть использован для данной платформы. В зависимости от обстоятельств ядро устанавливает точку входа в функцию, выполняющую системный вызов. Далее, функция экспортируется в пользовательское пространство ввиде библиотеки linux-gate.so.1 . Библиотека linux-gate.so.1 физически не существует на диске. Она, если можно так выразиться, эмулируется ядром и существует ровно столько, сколько работает система. Если выполнить останов системы, подмонтировать корневую ФС из другой системы, то Вы не найдёте на корневой ФС остановленной системы этот файл. Собственно, Вы не сможете его найти даже на работающей системе. Физически его просто нет. Именно поэтому linux-gate.so.1 - это нечто иное, как VDSO - т.е. Virtual Dynamically Shared Object. Ядро отображает эмулируемую таким образом динамическую библиотеку в адресное пространство каждого процесса. Убедиться в этом несложно, если выполнить следующую команду:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>f0x@devel0:~$ cat /proc/self/maps
08048000-0804c000 r-xp 00000000 08:01 46 /bin/cat
0804c000-0804d000 rw-p 00003000 08:01 46 /bin/cat
0804d000-0806e000 rw-p 0804d000 00:00 0 [heap]
...
b7fdf000-b7fe1000 rw-p 00019000 08:01 2066 /lib/ld-2.5.so
bffd2000-bffe8000 rw-p bffd2000 00:00 0 [stack]
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]</pre></span>Здесь самая последняя строка и есть интересующий нас объект: <span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]</pre></span>Из приведённого примера видно, что объект занимает в памяти ровно одну страницу - 4096 байт, практически на задворках адресного пространства. Проведём ещё один эксперимент:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>f0x@devel0:~$ ldd `which cat`
linux-gate.so.1 => (0xffffe000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e87000)
/lib/ld-linux.so.2 (0xb7fdf000)
f0x@devel0:~$ ldd `which gcc`
linux-gate.so.1 => (0xffffe000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e3c000)
/lib/ld-linux.so.2 (0xb7f94000)
f0x@devel0:~$</pre></span>Здесь мы просто навскидку взяли два приложения. Видно, что библиотека отображается в адресное пространство процесса по одному и тому же постоянному адресу - 0xffffe000. Теперь попробуем посмотреть, что же такое хранится на этой странице памяти на самом деле...</p><p>Сделать дамп страницы памяти, где хранится разделяемый код VDSO, можно с помощью следующей программы:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main () {
char* vdso = 0xffffe000;
char* buffer;
FILE* f;
buffer = malloc (4096);
if (!buffer)
exit (1);
memcpy (buffer, vdso, 4096);
if (!(f = fopen ("test.dump", "w+b"))) {
free (buffer);
exit (1);
}
fwrite (buffer, 4096, 1, f);
fclose (f);
free (buffer);
return 0;
}</pre></span>Строго говоря, раньше это можно было сделать проще, с помощью команды <strong>dd if=/proc/self/mem of=test.dump bs=4096 skip=1048574 count=1</strong>, но ядра начиная с версии 2.6.22 или, быть может, даже более ранней, больше не отображают память процесса в файл /proc/`pid`/mem. Этот файл, сохранён, очевидно, для совместимости, но не содержит более информации.</p><p>Скомпилируем и прогоним приведённую программу. Попробуем дизассемблировать полученный код:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>f0x@devel0:~/tmp$ objdump --disassemble ./test.dump
./test.dump: file format elf32-i386
Disassembly of section .text:
ffffe400 <__kernel_vsyscall>:
ffffe400: 51 push %ecx
ffffe401: 52 push %edx
ffffe402: 55 push %ebp
ffffe403: 89 e5 mov %esp,%ebp
ffffe405: 0f 34 sysenter
...
ffffe40e: eb f3 jmp ffffe403 <__kernel_vsyscall+0x3>
ffffe410: 5d pop %ebp
ffffe411: 5a pop %edx
ffffe412: 59 pop %ecx
ffffe413: c3 ret
...
f0x@devel0:~/tmp$</pre></span>Вот он наш шлюз для системных вызовов, весь, как на ладони. Процесс (либо, системная библиотека libc), вызывая функцию __kernel_vsyscall попадает на адрес 0хffffe400 (в нашем случае). Далее, __kernel_vsyscall сохраняет в стеке пользовательского процесса содержимое регистров ecx, edx, ebp, О назначении регистров ecx и edx мы уже говорили ранее, в ebp используется позже для восстановления стека пользователя. Выполняется инструкция sysenter, "перехват прерывания" и, как следствие, очередной переход на sysenter_entry (см. выше). Инструкция jmp по адресу 0xffffe40e вставлена для перезапуска системного вызова с числом 6 аргументами (см. <a href="http://lkml.org/lkml/2002/12/18/">http://lkml.org/lkml/2002/12/18/</a>). Код, размещаемый на странице, находится в файле arch/i386/kernel/vsyscall-enter.S (или arch/i386/kernel/vsyscall-int80.S для ловушки 0x80). Хотя я и нашёл, что адрес функции __kernel_vsyscall постоянный, но есть мнение, что это не так. Обычно, положение точки входа в __kernel_vsyscall() можно найти по вектору ELF-auxv используя параметр AT_SYSINFO. Вектор ELF-auxv содержит информацию, передаваемую процессу через стек при запуске и содержит различную информацию, нужную в процессе работы программы. Этот вектор в частности содержит переменные окружения процесса, аргументы, и проч..</p><p>Вот небольшой пример на С, как можно обратиться к функции __kernel_vsyscall напрямую:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#include <stdio.h>
int pid;
int main ()
{
__asm (
"movl $20, %eax \n"
"call *%gs:0x10 \n"
"movl %eax, pid \n"
);
printf ("pid: %d\n", pid);
return 0;
}</pre></span>Данный пример взят со страницы Manu Garg, <a href="http://www.manugarg.com">http://www.manugarg.com</a>. Итак, в приведённом примере мы делаем системный вызов getpid() (номер 20 или иначе __NR_getpid). Чтобы не лазить по стеку процесса в поисках переменной AT_SYSINFO воспользуемся тем обстоятельством, что системная библиотека libc.so при загрузке копирует значение переменной AT_SYSINFO в блок управления потоком (TCB - Thread Control Block). На этот блок информации, как правило, ссылается селектор в gs. Предполагаем, что по смещению 0х10 находится искомый параметр и делаем вызов по адресу, хранящемуся в %gs:$0x10.</p><h4>Итоги.</h4><p>На самом деле, практически, особого прироста производительности даже при поддержке на данной платформе FSCF (Fast System Call Facility) добиться не всегда возможно. Проблема в том, что так или иначе, процесс редко обращается напрямую к ядру. И для этого есть свои веские причины. Использование библиотеки libc позволяет гарантировать переносимость программы вне зависимости от версии ядра. И именно через стандартную системную библиотеку идёт большинство системных вызовов. Если даже Вы соберёте и установите самое последнее ядро, собранное для платформы, поддерживающей FSCF, это ещё не гарантия прироста производительности. Дело в том, что Ваша системная библиотека libc.so будет попрежнему использовать int 0x80 и справиться с этим можно лишь пересобрав glibc. Поддерживается ли в glibc вообще интерфейс VDSO и __kernel_vsyscall, я, честно признаться, на данный момент ответить затрудняюсь.</p><h4>Ссылки.</h4><p>[1] Manu Garg's page, <a href="http://www.manugarg.com">http://www.manugarg.com</a><br />
[2] Scatter/Gather thoughts by Johan Petersson, <a href="http://www.trithium.com/johan/2005/08/linux-gate/">http://www.trilithium.com/johan/2005/08/linux-gate/</a><br>[3] Старый добрый Understanding the Linux kernel Куда же без него :)<br>[4] Ну и конечно же, исходные коды Linux (2.6.22)<br></p><p>2008-03-18. Further revisions are possible...</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com4tag:blogger.com,1999:blog-3876463594612412036.post-62137975740789457622007-11-29T10:05:00.000-08:002007-11-29T10:14:57.857-08:00Toshiba Satellite A100-811: ещё немного о Bluetooth. Удалённый доступ.<p>Итак, мы настроили устройство Bluetooth-адаптера. Что дальше? Ну, для начала, если Ваш телефон умеет GPRS и у Вас есть подписка, можно настроить удалённый доступ через GPRS-модем, в роли которого будет выступать Ваш телефон, оснащённый Bluetooth. В принципе, всё, о чём пойдёт речь ниже - вполне применимо и к настройке доступа с помощью GPRS + IrDA. Разве что во втором случае в качестве модема будет использоваться устройство ircomm, а не знакомый нам уже rfcomm.</p><p>Самый простой способ ИМХО, это настроить соединение в какой-нибудь звонилке, а ля kppp. Хотя, можно сделать это и с помощью скриптов ppp. Описание второго способа попадалось мне довольно часто, поэтому я думаю, у желающих и без меня будет масса возможностей настроить доступ с помощью скриптов. Я приведу пример, но подробно останавливаться на деталях не буду. С kppp и того проще. Моя цель лишь указать на некоторые вещи, которые мешают установить работоспособное соединение и которые не всегда очевидны для начинающих пользователей.</p><p>Будем исходить их того, что Bluetooth у Вас уже настроен, как рассказывалось ранее и есть конфиг для устройства, эмулирующего последовательную линию - rfcomm (для IrDA это будет устройство ircomm). На настройках телефона останавливаться не будем. Они в любом случае не универсальны и будут зависеть от Вашего оператора, которого и надо будет терроризировать на предмет информации по этому поводу :)</p><p>Первое, что мы сделаем, это убедимся, что телефон предоставляет услугу удалённого доступа (для Bluetooth это профиль DUN, для IrDA всё немного проще, ибо у IrDA устройств нет профилей, как это имеет место с Bluetooth). При включенном Bluetooth-адаптере запускаем:
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>root@devel0:/home/f0x# hcitool scan
Scanning ...
00:0E:07:DC:F8:3E Red Quick Fox
root@devel0:/home/f0x# hcitool inq
Inquiring ...
00:0E:07:DC:F8:3E clock offset: 0x71ce class: 0x520204
root@devel0:/home/f0x# sdptool browse
Inquiring ...
Browsing 00:0E:07:DC:F8:3E ...
Service Name: Dial-up Networking
Service RecHandle: 0x10000
Service Class ID List:
"Dialup Networking" (0x1103)
"Generic Networking" (0x1201)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 1
Profile Descriptor List:
"Dialup Networking" (0x1103)
Version: 0x0100
Service Name: Voice gateway
Service RecHandle: 0x10002
Service Class ID List:
"Headset Audio Gateway" (0x1112)
"Generic Audio" (0x1203)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 3
Profile Descriptor List:
"Headset" (0x1108)
Version: 0x0100
Service Name: Serial Port 1
Service RecHandle: 0x10003
Service Class ID List:
"Serial Port" (0x1101)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 4
Service Name: Serial Port 2
Service RecHandle: 0x10004
Service Class ID List:
"Serial Port" (0x1101)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 5
Service Name: OBEX Object Push
Service RecHandle: 0x10005
Service Class ID List:
"OBEX Object Push" (0x1105)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 10
"OBEX" (0x0008)
Profile Descriptor List:
"OBEX Object Push" (0x1105)
Version: 0x0100
Service Name: IrMC Synchronization
Service RecHandle: 0x10006
Service Class ID List:
"IrMC Sync" (0x1104)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 11
"OBEX" (0x0008)
Profile Descriptor List:
"IrMC Sync" (0x1104)
Version: 0x0100
Service Name: HF Voice gateway
Service RecHandle: 0x10007
Service Class ID List:
"Handfree Audio Gateway" (0x111f)
"Generic Audio" (0x1203)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 6
Profile Descriptor List:
"Handsfree" (0x111e)
Version: 0x0100
Service Name: OBEX Basic Imaging
Service RecHandle: 0x1000b
Service Class ID List:
"Imaging Responder" (0x111b)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 15
"OBEX" (0x0008)
Profile Descriptor List:
"Imaging" (0x111a)
Version: 0x0100
Service Name: OBEX File Transfer
Service RecHandle: 0x1000f
Service Class ID List:
"OBEX File Transfer" (0x1106)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 7
"OBEX" (0x0008)
Profile Descriptor List:
"OBEX File Transfer" (0x1106)
Version: 0x0100
</pre></span><p>Первой командой мы просто сканируем эфир в поисках другого Bluetooth-устройства и находим его. Второй командной смотрим, что это за устройство - класс. Хотя второе - это скорее чисто эстетический манёвр для маньяков :) И наконец с помощью третьей команды мы узнаём, какие сервисы нам предоставляет удалённое устройство. Первый блок информации и есть то, что нам нужно:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>Service Name: Dial-up Networking
Service RecHandle: 0x10000
Service Class ID List:
"Dialup Networking" (0x1103)
"Generic Networking" (0x1201)
Protocol Descriptor List:
"L2CAP" (0x0100)
"RFCOMM" (0x0003)
Channel: 1
Profile Descriptor List:
"Dialup Networking" (0x1103)
Version: 0x0100</pre></span>Это наш профиль DUN (Dial-Up Networking).</p><p>Чобы создать соединение РРР, в /etc/ppp/peers создайте файл с именем новой точки. В этот файл мы свалим параметры для нового соединения. Скорее всего (во всяком случае, так обстоит дело с моим оператором), нам придётся отключить всякое сжатие. Должно получиться что-то вроде этого:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>/dev/rfcomm0 # [ устройство, эмулирующее последовательною линию RS-232 по Bluetooth ]
115200
connect "/usr/sbin/chat /etc/chatscripts/gprs"
noauth
defaultroute
lock
debug # [ вставляем на всякий случай, пока всё не будет обкатано ]
novjccomp
nopcomp
noaccomp
nodeflate
novj
noccp
nobsdcomp
default-asyncmap
ipcp-accept-local
ipcp-accept-remote
lcp-echo-failure 100
lcp-echo-interval 30
usepeerdns
user <user_name></pre></span>Здесь user_name - имя пользователя, под которым Вы будете стучаться на РРР-сервер Вашего оператора. Как показывает практика, у некототорых операторов есть определённые ограничения на работу с PPP при доступе по GPRS. Это касается прежде всего поддержки сжатия (специально наматывают траф? :( ). Отключаем, всё что можно:<ol><li>novjccomp, novj - отключить сжатие TCP/IP по Ван Якобсону.</li><li>nobsdcomp - отключить сжатие пакетов по схеме BSD.</li><li>nodeflate - отключить сжатие Deflation.</li><li>noaccomp - отключить сжатие адресов/управляющих пакетов.</li><li>nopcomp - отключить согласование сжатия между удалённой точкой и нашей машиной.</li><li>noccp - отключить протокол управления сжатием.</li></ol>Далее, часто встречается ситуация, когда удалённая точка по каким-либо причинам не в состоянии корректно обеспечить управление линией (на уровне LCP - Link Control Protocol - протокол управления линией). Как правило, это выражается в том, что клиентская машина посылает LCP-запросы, чтобы выяснить состояние линии, но не получает отклика от сервера. В подобном случае содинение разрывается по инициативе клиента, т.к. pppd исходит из того, что линия неработоспособна. Насколько это хорошо, сказать сложно. Но изменить поведение демона, отключив ожидание откликов не представляется возможным. Я более склонен возложить вину на оператора, нежели на демон pppd, ибо ИМХО это вполне в силах оператора обеспечить корректную работу ppp-сервера.</p><p>Работа LCP в частности управляется параметрами lcp-echo-failure и lcp-echo-interval (на самом деле, параметров LCP, конечно, немного больше). Первый (lcp-echo-failure) задаёт максимальное количество попыток, по истечении которого считается, что произошёл сбой линии. Второй параметр (lcp-echo-interval) определяет временной интервал между посылками LCP-запросов, на которые удалённый сервер должен прислать своё эхо. Время устанавливается в секундах. В нашем случае параметры имеют значения 100 и 30 соответственно, т.е. если ни на один из посланных 100 запросов не был получен отклик, при том, что запросы отправляются с периодичностью в 30 с, то линия считается мёртвой и демон разрывает соединение. При этом в лог сыплется что-то вроде этого (при включении debug):<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>Nov 3 23:55:57 devel0 pppd[19993]: pppd 2.4.4 started by f0x, uid 1000
Nov 3 23:55:57 devel0 pppd[19993]: Using interface ppp0
Nov 3 23:55:57 devel0 pppd[19993]: Connect: ppp0 <--> /dev/rfcomm0
Nov 3 23:55:57 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 0
Nov 3 23:55:57 devel0 pppd[19993]: PAP authentication succeeded
Nov 3 23:55:58 devel0 pppd[19993]: local IP address 10.20.85.219
Nov 3 23:55:58 devel0 pppd[19993]: remote IP address 212.98.170.50
Nov 3 23:55:58 devel0 pppd[19993]: primary DNS address 77.74.32.66
Nov 3 23:55:58 devel0 pppd[19993]: secondary DNS address 77.74.32.11
Nov 3 23:56:27 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 1
Nov 3 23:56:57 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 2
Nov 3 23:57:27 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 3
...
Nov 4 00:43:29 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 95
Nov 4 00:43:59 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 96
Nov 4 00:44:29 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 97
Nov 4 00:44:59 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 98
Nov 4 00:45:59 devel0 pppd[19993]: No response to 100 echo-requests</pre></span></p><p>lock - традиционный параметр, предписывающий pppd создавать файл блокировки и т.о. обеспечивает исключительный доступ к соединению. defaultroute - установить новый маршрут к PPP-шлюзу, полученный при установке соединения, маршрутом по умолчанию. usepeerdns - использовать DNS удалённой точки, позволяет не заботиться о поиске собственного DNS :) noauth - не требовать от удалённой точки авторизации. ipcp-accept-local и ipcp-accept-remote - параметры, определяющие, будут ли приняты адреса удалённой точки и нашей машины по инициативе удалённого сервера, даже если локально адреса уже были заданы. Так в общих чертах обстоит дело с конфигурированием соединения с удалённой точкой - РРР-сервером.</p><p>Далее, в случае с настройкой соединения с помощью скриптов идёт скрипт чата, определяющий реакцию на состояния линии ("занято", "звонок", "удалённый модем положил трубку" и т.п.). Здесь же прописывается номер, который набирает модем. В общих чертах скрипт чата выглядит так:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>ECHO ON
ABORT '\nBUSY\r'
ABORT '\nERROR\r'
ABORT '\nNO ANSWER\r'
ABORT '\nNO CARRIER\r'
ABORT '\nNO DIALTONE\r'
ABORT '\nRINGING\r\n\r\nRINGING\r'
'' \rAT
TIMEOUT 12
OK ATH
OK ATE1
OK AT+CGDCONT=1,"IP","internet-address"
OK ATD*99*1#
TIMEOUT 22
CONNECT</pre></span>Кладём этот файл в /etc/chatscripts/ под именем, ну, хотя бы gprs (за более подробными сведениями обращайтесь к man-страницам). Далее, в файле /etc/ppp/pap-secrets прописываем имя пользователя и пароль:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>user_name * password</pre></span>где user_name и password - соответственно имя пользователя и пароль, под которыми Вы ходите на сервер оператора. В общих чертах, всё. Теперь можно устанавливать соединение командой pppd call peer-name, где peer-name - имя точки доступа в /etc/ppp/peers.</p><p>Настройка доступа с помощью kppp отличается лишь тем, что параметры конфигурации для pppd и прочее нужно прописывать не в файлах ppp, а в диалогах kppp. В качестве устройства модема при конфигурировании устройства в kppp указывайте /dev/ircomm0 (или другое число вместо нуля в зависимости от того, как у вас настроена подсистема ircomm).</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-62322854163614088612007-11-11T14:52:00.000-08:002007-11-11T14:58:58.374-08:00Toshiba Satellite A100-811: Core 2 Duo и частота ядер после возврата из suspend-to-RAM (solved).<p>Довольно долгое время у меня на ноуте под управлением Ubuntu 7.04 наблюдался такой странный глюк. Во время работы от аккумулятора при засыпании ноута в память (suspend-to-RAM) и по возвращении из сна одно ядро процессора работало на пониженной частоте, как и положено в режиме экономии, а другое - почему-то начинало работать на полных оборотах. На просторах интернета я нашёл следующее решение: накатать скрипт следующего содержания:<span style="color: rgb(0, 0, 153); font-family: courier new;">
<pre>#!/bin/sh
SYS_DIR=/sys/devices/system/cpu
POLICY=powersave
# ondemand
for CPU in `ls $SYS_DIR`
do
echo -n "$POLICY" > $SYS_DIR/$CPU/cpufreq/scaling_governor
done</pre></span>и положить его под именем <priority>-<name> в папку /etc/acpi/resume.d. Здесь priority - это число, определяющее порядок выполнения скрипта. Как и в директориях rc?.d файлы обрабатываются демоном acpid в алфавитном порядке и чтобы задать приоритет, необходимо приписать соответствующий префикс. Пусть в нашем случае это будет 10-core2duo-freq-fix.sh</p><p>Не понадобилось много времени, чтобы заметить, что данный способ не работает. Долго и упорно я пытался найти решение, копаясь в спецификации ACPI, в частности, производя поиски на предмет генерируемых событий. Много чего нового узнал, но того, что надо, так и не нашёл. Я не мог понять, почему в моей системе не генерируется событие по выходу из сна - resume... И не мог понять, отдаётся ли такое событие в принципе и предусмотрено ли оно самим ACPI. И хотя ответ на этот вопрос сразу найден не был, решение задачи оказалось гораздо проще и лежало оно в немного другой области. Собственно, решение мне подсказали на форуме и было даже немного обидно, до того всё оказалось очевидным :)<br>Чтобы понять все "что, как и почему" и вникнуть в суть, давайте немного пройдёмся по матчасти.<br><br><strong>ACPI</strong><br>Во времена оные всё было иначе - солнышко светило ярче, трава была зеленее и забористее, деревья большие, а для управления питанием использовался APM - интерфейс Advanced Power Management. APM - не что иное, как "расширение" BIOS'а по управлению питанием. Или говоря иначе, в системах с APM было устройство /dev/apm, на котором можно было сделать ioctl(), а остальное уже выполнялось железом. APM работал не идеально, но работал. Кто-то пришёл к выводу, что это не есть хорошо и так появился ACPI - Advanced Configuration and Power Interface. В отличие от APM, ACPI возлагает часть работы на операционную систему. Поэтому, строго говоря, если с APM всё было достаточно просто, т.к. со стороны ОС требовалась лишь поддержка интерфейса APM и вся работа выполнялась уже вне ядра ОС аппаратным обеспечением, то для нормальной работы энергосбережения через ACPI необходима корректная реализация этого самого ACPI в ОС, что несколько усложняет вещи. Но с другой стороны у ACPI есть преимущество перед APM. Если APM - это функция железа, то ACPI - это функция во многом ОС. Что легче заменить в случае неправильной работы? Железо или ядро ОС? Не будем углубляться в тонкости работы обоих интерфейсов. Сосредоточимся на ACPI. Как было упомянуто, тут ядро выполняет гораздо больше работы, чем в случае с APM. В частности, это позволяет переопределить поведение системы в некоторых случаях, где это было сложно или невозможно с APM. Работа ACPI управляется во многом событиями. Допустим такой пример: пользователь нажимает кнопку "power", это генерирует прерывание, которое перехватывается встроенным контроллером. Реакцией на прерывание становится выставление флагов. Таким образом, ОС уведомляется о том, что что-то произошло. Далее, ОС смотрит, что должно произойти дальше согласно DSDT и передаёт сообщение пользовательскому процессу в виде события. В роли процесса обычно выступает демон acpid, который читает файл /proc/acpi/event. Далее, acpid выполняет какие-то предопределённые конфигурацией действия. В общих чертах схема такова.</p><p><strong>Suspend, как он есть.</strong><br>Спецификация ACPI определяет набор так называемых состояний (глобальных - Gx и сна - Sx). suspend-2-ram - одно из S-состояний системы. В случае с переходом в suspend дела могут обстоять несколько иначе, чем было описано выше. В частности, если пользователь нажимает какую-то кнопку в диалоге выхода из системы, то процесс идёт в обратном направлении. Сам по себе процесс засыпания на этот раз инициируется пользовательским процессом - всё тем же acpid, допустим. Он выполняет некие действия, предусмотренные конфигурацией. Пусть была нажата кнопка перехода в состояние suspend-2-ram, происходит приблизительно следующее:<ol><LI>acpid поучает сообщение через unix-сокет. Как гласит файл /etc/acpi/events/sleepbtn, обработчиком события является скрипт /etc/acpi/sleep.sh:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># /etc/acpi/events/sleepbtn
# Called when the user presses the sleep button
# [ Здесь-то мы и определяем обработчик события ]
event=button[ /]sleep
action=/etc/acpi/sleep.sh</pre></span></LI><li>Далее управление передаётся скрипту /etc/acpi/sleep.sh. Мы не будем рассматривать его здесь очень детально, скажем лишь, что этот скрипт вполне может умыть руки и передать управление gnome-power-manager'у или klaptopdaemon'у например, если кто-то из них запущен и не определено форсирование выполнения обработчика ACPI - /etc/acpi/sleep.sh. В противном случае, если демоны Gnome или KDE не работают или необходимо форсировать выполнение /etc/acpi/sleep.sh, далее по очереди вызывается скрипт prepare.sh. Он подготавливает пользовательское окружение к переходу в S-состояние. Всё, что делает этот скрипт - выполняет скрипты, сложенные в директории /etc/acpi/suspend.d:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>#!/bin/bash
for SCRIPT in /etc/acpi/suspend.d/*.sh; do
. $SCRIPT
done</pre></span></li><li>Далее идёт кое-какая рутина. И, наконец, самое интересное, в конце... То, чего я не заметил и то, что оказалось так очевидно. Вызывается скрипт
/etc/acpi/resume.sh, который тянет в свою очередь всё, что лежит в директории /etc/acpi/resume.d. Знакомое имя? :)</li><li>acpid просит ядро заснуть. И наконец, очередь доходит до ядра, которое выполняет всю чёрную работу - останавливает и замораживает процессы и переводит железо в S-состояние. Теперь мы спим.</li></ol></p><p>Хотя, на самом деле, есть событие ACPI, которое генерируется при пробуждении (Wakeup) - WAK, но как мы увидели, обработчика такого события у acpid нет. Всё сделано гораздо проще. Выполнение скрипта приостанавливается как раз в той точке, где в специальный файл записывается значение, определяющее тип сна - echo -n $ACPI_SLEEP_MODE >/sys/power/state. При пробуждении нам не нужны никакие обработчики, потому что скрипт продолжает выполняться со следующей строки и вполне логично доходит до вызова скриптов, что находятся в директории /etc/acpi/resume.d. Довольно красиво и просто. Итак, мы ответили на два вопроса - "что?" и "как?". Остался ещё один - почему не работает наш скрипт, помещённый в /etc/acpi/resume.d? Нет, конечно же он работает, если запускать его вручную. Но это не наш метод. Ответ на самом деле уже был дан. Осталось придать ему более чёткую формулировку.</p><p><strong>KDE, Gnome и HAL</strong><br>HAL - Hardware Abstraction Layer - предоставляет ещё один метод абстракции от железа и управления питанием. В десктоп-менеджерах, Gnome и KDE часто работают свои менеджеры питания. У меня, в частности, работает утилитка из kde-guidance-powermanager. Она не пользуется услугами acpid, используя вместо него HAL. HAL использует и gnome-power-manager. Дальше всё просто. Можно считать, решение задачи найдено. Мы уже знаем, где находятся скрипты HAL: /usr/lib/hal/scripts/linux. Здесь нам нужен скрипт hal-system-power-suspend-linux. Выполняется он по такому же принципу, как и скрипт-обработчик ACPI - т.е., доходит до определённой точки, усыпляет систему и продолжает выполнение далее по пробуждении. Вот этим-то мы и воспользуемся, добавив в конец скрипта такие строки:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>for x in /proc/acpi/ac_adapter/*; do
grep -q off-line $x/state
if [ $? = 0 ] && [ x$1 != xstop ]; then
/etc/acpi/resume.d/10-dualcore-freq.sh
fi
done</pre></span>Здесь мы проверяем, работает ли ноутбук от аккумулятора. Если мы работаем от сети, то ничего делать не надо. Профиль производительности cpufreq как был performance, так и остаётся. В противном случае, если мы работаем на аккумуляторе, сбиваем частоту ядер записью профиля powersave в /sys/devices/system/cpu/*/cpufreq/scaling_governor. Выравниваем частоту обоих ядер, чтобы не ошибиться :), так как ничего страшного в этом нет и искать ядро-спринтер нет нужды. Теперь всё должно работать. Мысленно поздравьте себя ;)</p><br>При написании частично использовалась статья <a href="http://www.advogato.org/article/913.html">http://www.advogato.org/</a>. Большое спасибо аффтару.red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-20386773418956618262007-11-11T13:18:00.000-08:002011-01-22T12:44:05.390-08:00udev в GNU/Linux<span style="font-weight: bold; color: rgb(0, 0, 153);">Содержание</span><br /><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 1 udev - реализация devfs на уровне пользователя</span><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 2 Вступление</span><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 3 /dev</span><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 4 Проблемы. Какому файлу в /dev соответствует какое устройство?</span><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 5 Недостаточно номеров</span><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 6 объём статического /dev слишком велик</span><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 7 devfs</span><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 8 Цели, стоящие перед udev</span><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 9 namedev</span><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 10 libsysfs</span><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);"> * 11 udev</span><br /><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);">udev - реализация devfs на уровне пользователя</span><br /><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);">Вступление</span>
<p align="justify">Начиная с ядра версии 2.5 все физические и виртуальные устройства в системе видны пользователю через файловую систему sysfs, которая представляет все устройства ввиде иерархической древовидной структуры. Когда в системе появляется новое устройство или удаляется старое, hotplug уведомляет об этом окружение пользователя. Используя два этих момента, можно создать пользовательскую реализацию динамической файловой системы в /dev, которая предоставляла бы более гибкие правила именования устройств, нежели это было в devfs. В этом документе обсуждается udev - программа, которая заменяет devfs без потерь для функциональности системы. Более того, udev создаёт в /dev файлы только для тех устройств, которые присутствуют на данный момент в системе. udev предоставляет и новые возможности, которых не было в devfs:</p>
1. постоянно закреплённые за устройствами имена, которые не зависят от того, какое положение они занимают в дереве устройств.<br />
2. уведомление внешних по отношению к ядру программ, если устройство было заменено.<br />
3. гибкие правила именования устройств.<br />
4. динамическое использование нижних и верхних номеров устройств.<br />
5. переход правил именования устройств из ядра в пространство пользователя.<br />
<p align="justify">Этот документ объясняет, почему программа, выполняемая в режиме пользователя предпочтительнее чем devfs, которая работает в режиме ядра. Далее также будет объяснено, как работает udev, как создавать для неё расширения (разные схемы именования устройств).</p>
<span style="font-weight: bold; color: rgb(0, 0, 153);">/dev</span>
<p align="justify">На каждом Linux-box'е /dev - это директория, в которой находятся файлы устройств, известных системе. Файл устройства - это интерфейс между пользовательскими приложениями и аппаратурой, механизм, предоставляемый ядром для доступа к физическим устройствам системы. Например устройство /dev/hda, как правило, представляет первый (master) IDE-накопитель на первичном канале. Имени hda соответствуют два числа - верхний и нижний номер устройства. Эти номера используются ядром для того, чтобы узнать, к какому физическому устройству необходимо обратиться (да, имена устройств существуют лишь для отвода глаз, реально же используются номера). Обычно каждому типу устройств отводится свой диапазон номеров. На данный момент есть ряд устройств одного типа с разными именами, но одинаковыми номерами (правда, их не так уж и много). Присвоение имён и номеров разным устройствам было проведено LANANA - The Linux Assigned Names And Numbers Authority. Список всех устройств можно найти в http://www.lanana.org/docs/device-list/devices.txt или в директории с исходными кодами ядра в Documentation/devices.txt. По мере того как в ядре появляется поддержка новых устройств, им необходимо выделать новые диапазоны номеров, с тем чтобы к ним можно было получить доступ через /dev. Одна из возможностей преодолеть дефицит номеров в статической /dev - организовать её как файловую систему (devfs). В ядрах серии 2.4 и более ранних диапазоны доступных номеров были 1-255 для нижней и 1-255 для верхней части.</p>
<span style="color: rgb(0, 0, 153); font-weight: bold;">Проблемы. Какому файлу в /dev соответствует какое устройство?</span>
<p align="justify">Когда ядро обнаруживает устройство, оно присваивает ему нижний/верхний номер, соответствующий типу устройства. Допустим, при загрузке ядро обнаружило USB-принтер. Оно присвоило этому устройству номера major:180 minor:0, в /dev этому устройству будет соответствовать файл /dev/usb/lp0. Второй USB-принтер получает номера 180:1 и к нему можно обращаться как к /dev/usb/lp1. Но если пользователь изменит топологию USB-устройств, скажем добавив USB-хаб? Тогда при следующей загрузке порядок обнаружения ядром принтеров может измениться. Принтер, на который раньше ссылались как на /dev/usb/lp1 превратиться в /dev/usb/lp0 и наоборот. Это справедливо и для устройств, которые могут быть подключены или удалены во время работы компьютера. Сказанное касается шин PCI hotplug, IEEE 394, USB, CardBus. Появление sysfs в ядрах 2.5 позволяет узнать какое именно устройство данного класса "скрывается" за данным minor-номером. Для системы с двумя подключенными USB-принтерами sysfs sys/class/usb может выглядеть следующим образом</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
/sys/class/usb
|-- lp0
| |--dev
| |--device -> ../../../devices/pci0/00:09.0/usb1/1-1/1-1:0
| +--driver -> ../../../bus/usb/drivers/usblp
+-- lp1
|--dev
|--device -> ../../../devices/pci0/00:0d.0/usb3/3-1/3-1:0
+--driver -> ../../../bus/usb/drivers/usblp
$ cat /sys/class/usb/lp0/device/serial HXOLL0012202323480 $cat /sys/class/usb/lp1/device/serial W09090207101241330</pre></span>
<p align="justify">Симлинки lp0/device и lp1/device ссылаются на директории, содержащие дополнительную информацию об устройствах. Таким образом можно выяснить производителя устройства и его (устройства) уникальный (надеемся) серийный номер. Как мы выяснили, принтеру lp0 соответствует S/N HXOLL0012202323480, а lp1 - W09090207101241330. Если поменять порядок подключения принтеров или кинуть их через хаб, поменяются и их имена (но не серийные номера, конечно!). Вот что у нас получится:</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
/sys/class/usb
|-- lp0
| |--dev
| |--device -> ../../../devices/pci0/00:09.0/usb1/1-1/1-1.1/1-1.1:0
| +--driver -> ../../../bus/usb/drivers/usblp
+-- lp1
|--dev
|--device -> ../../../devices/pci0/00:09.0/usb1/1-1/1-1.4/1-1.4:0
+--driver -> ../../../bus/usb/drivers/usblp
$ cat /sys/class/usb/lp0/device/serial W09090207101241330 $cat /sys/class/usb/lp1/device/serial HXOLL0012202323480</pre></span>
<p align="justify">Теперь принтеру lp0 соответствует S/N W09090207101241330, а lp1 - HXOLL0012202323480. sysfs позволяет с лёгкостью узнать, какому именно устройству соответствует какой файл. Это достаточно мощный механизм сопоставления физическим устройствам файлов устройств в /dev. Однако пользователя не интересует то, что lp0 и lp1 поменялись местами и что не мешало бы внести изменения в некоторые конфигурационные файлы. Пользователь просто хочет печатать на том принтере, на котором ему нужно, не задумываясь, к какому порту подключено USB-устройство и не подключено ил оно вообще через хаб.</p>
<span style="font-weight: bold; color: rgb(0, 0, 153);">Недостаточно номеров</span>
<p align="justify">Major- и minor-части номера устройства занимают по 8 бит (т.е., как было уже упомянуто 1-255 для верхней и столько же для нижней части). На данный момент для символьных и блочных устройств осталось не так уж и много major-номеров. Символьные и блочные устройства могут использовать один и тот же major-номер, ядро относит их к разным типам, поэтому конфликтов не возникает. Казалось бы, ещё море свободных номеров. Но на некоторых системах может использоваться, скажем, 4000 дисков, а 8 бит нижней части номера для этого уже явно не достаточно. Для каждого диска ядро резервирует 16 младших номеров, т.к. на диске может быть до 16 разделов. 4000 дисков требуют 64000 (!) файлов-устройств (не хило, не так ли, господа?). Но для того чтобы получить к ним доступ, есть лищь 255 номеров. Конечно и 8 битовая схема сработала бы, если распределить все диски по всему диапазону от 1 до 65535, но ведь большинство старших номеров уже зарезервировано. Дисковая подсистема не может просто оттяпать кусок из диапазона старших номеров. Если же это произошло, необходимо уведомить окружение пользователя, что отныне часть старших номеров переназначена под использование подсистемой SCSI. Из-за этого ограничения многие настаивают на увеличении диапазонов старших и младших номеров. Не стоит забывать и о том, что 4000 дисков это ещё не предел, на некоторых системах есть необходимость подключить одновременно 10000 накопителей. Похоже, что увеличение разрядности номера устройства могло бы произойти в ядрах серии 2.6, но по прежнему проблемой остаётся уведомление пользовательского окружения, какие major- и minor-номера заняты, а какие нет. Даже если диапазон верхних половин номеров будет увеличен, необходимо определить, какому устройству соответствует какой участок этого диапазона. Это требует наличия некоего органа, который будет заниматься распределением блоков номеров и стандартизированием имён файлов-устройств. Кроме того, неисключено, что в будущем и этот ресурс будет исчерпан. Если бы ядро автоматически распределяло диапазоны старших и младших номеров между устройствами, которые представлены в данный момент в системе, необходимость в таком органе отпала бы и верхний предел номеров, вероятно, не будет превышен. Идея с динамическим распределением номеров наталкивается на одно "но": окружение пользователя по прежнему не имеет понятия, какое оборудование подключено к машине и какие номера соответствуют каким устройствам. Пока ещё лишь немногие подсистемы ядра умеют динамически распределять номера устройств. Так например с успехом поступает подсистема USB2Serial (ещё со времён ядер серии 2.2). И вновь старая проблема, неведение пользователя. Было бы бесчеловечно заставлять его просматривать логи ядра, чтобы выяснить, какому устройству соответствуют определённые номера... sysfs, появившаяся в ветке 2.5, позволяет легко выяснить то, что нам так нужно.</p>
<span style="font-weight: bold; color: rgb(0, 0, 153);">Объём статического /dev слишком велик</span>
<p align="justify">Далеко не все устройства, для которых есть файлы в /dev рально присутствуют в системе. Некоторые могут быть просто не подключены на данный момент, а некоторых в ней никогда и не было. /dev как правило генерируется при установке ОС на жёстский диск и содержит файлы для всех устройств, известных на данный момент. На машине с установленным Red Hat 9 около 18000 файлов устройств. Иногда это сбивает с толку пользователей, которые пытаются определить, какие устройства реально присутствуют в системе. Поэтому в некоторых Unix-like ОС и дистрибутивах Linux управление /dev отдаётся на откуп ядру, которое знает, какие устройства подключены и которое создаёт в памяти devfs-систему. Такая схема стала достаточно популярной.</p>
<span style="font-weight: bold; color: rgb(0, 0, 153);">devfs</span>
<p align="justify">devfs используется в некоторых Unix-like ОС. Её реализация есть и в Linux. Однако в Linux devfs по-прежнему не решает многих проблем. devfs содержит лишь те устройства, которые представлены в системе и таким образом решает проблему с диким объёмом /dev. Но имена файлов в devfs не соответствуют именам, утверждённым LANANA. По этой причине полный переход со статического /dev на динамическую devfs проблематичен. Необходимы кардинальные изменения в файлах конфигурации. Создатели devfs попытались решить эту проблему, введя дополнительный слой совместимости (devfsd-демон), чтобы эмулировать /dev. Однако при том, что совместимость достигнута, правила именования устройств находятся в ведении ядра. Первый IDE-накопитель на первичном канале будет называться /dev/hda или /dev/ide/hd/c0b0t0u0 (лично у меня - /dev/ide/host0/bus0/target0/lun0/disc) и тут ничего не сделаешь. Вообще разработчики ядра не любят выносить правила именования устройств за пределы ядра. Но правила присвоения имён должны быть убраны из ядра. Это позволит разработчикам драйверов не заострять внимание на схемах именования. Короче, ядро не должно волновать то, как пользователь называет устройство. В случае с devfs это невозможно. devfs не позволяет распределять номера устройств динамически, в ней используются номера определённые LANANA. Хотя devfs можно модифицировать, чтобы она позволяла использовать динамические номера, никто до сих пор этого не сделал. Все данные (база данных, имена) devfs хранит в памяти ядра, которая не выгружается на диск. При больших количествах подключенных устройств (те же 4000 дисков) затраты памяти становятся весьма существенными.</p>
<span style="font-weight: bold; color: rgb(0, 0, 153);">Цели, стоящие перед udev</span>
<p align="justify">В свете упомянутых проблем началась работа на проектом udev. Цели этого проекта следующие:</p>
1 переход в пространство пользователя<br />
2 создание динамического /dev<br />
3 предоставление пользователю возможности, самому формировать и назначать имена файлов-устройств<br />
4 создание пользовательского API для доступа к информации об устройствах, подключённых к системе
<p align="justify">Переход в режим пользователя возможен благодаря тем фактам, что hotplug генерирует события при добавлении или удалении устройств, а с помощью sysfs получить информацию об этих устройствах не сложно. Остальные цели позволяют разбить проект на три подсистемы:</p>
1 namedev - работа с именами устройств<br />
2 libsysfs - библиотека доступа к информации в sysfs<br />
3 udev - динамический аналог /dev<br /><br />
<span style="font-weight: bold; color: rgb(0, 0, 153);">namedev</span>
<p align="justify">Ввиду необходимости разных схем именования, эта часть проекта была выделена в отдельную подсистему. Это было сделано чтобы отграничить, собственно udev от правил назначения имён и обеспечить поддержку подключаемых схем другими группами разработчиков. Подсистема именования, namedev, - это стандартный интерфейс, который udev использует, чтобы присвоить имя данному устройству. В первых релизах проекта namedev пока компонуется в один исполняемый файл с udev. Вместе с этими релизами идёт и простая схема именования, основанная на спецификации LANANA. Она скорее всего подойдёт большинству пользователей Linux. namedev позволяет присвоить устройству имя исходя из пяти признаков:</p>
1 метка или серийный номер<br />
2 номер шины<br />
3 номер устройства на шине<br />
4 другое (подстановочное) имя<br />
5 имя устройства в ядре
<p align="justify">Первый признак - метка или серийный номер, проверяется при подключении устройства. Этот признак интерпретируется в зависимости от типа устройства. В случае SCSI - это UUID, для USB - серийный номер, для прочих блочных устройств - метка файловой системы. Второй признак - номер шины. Номер шины на большинстве систем меняется редко. Это может произойти при апгрейде BIOS или при подключении hotplug-контроллера. В кажды момент времени номер шины постоянен и уникален. Трерий признак - топология шины. Это может оказаться полезным в случае с USB-шиной. Подстановочное имя используется вместо имени устройства в ядре. И, наконец, если ни один из 4 предыдущих признаков не прошёл, то используется имя устройства в ядре. Это имя совпадает с именем, которое использовалось бы на системе без devfs или udev со статическим /dev. Вот пример конфига, который позволяет назначить устройствам имена, отличные от тех, которые использует ядро:</p>
<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>
1. USB Epson printer to be called lp_epson
LABEL, BUS="usb", serial="HXOLL0012202323480", NAME="lp_epson"
1. USB HP printer to be called lp_hp
LABEL, BUS="usb", serial=@W09090207101241330", NAME="lp_hp"
1. sound card with PCI bus id 00:0b.0 to be the first
NUMBER, BUS="pci", id="00:0b.0", NAME="dsp"
1. sound card with PCI bus id 00:07.1 to be the second
NUMBER, BUS="pci", id="00:0b.0", NAME="dsp1"</pre></span>
<span style="font-weight: bold; color: rgb(0, 0, 153);">libsysfs</span>
<p align="justify">libsysfs предоставляет интерфейс для доступа к данным на sysfs. Собственно libsysfs может быть использована не только в проекте udev. Подсистема присвоения имён udev должна запрашивать из sysfs довольно много информации о подключённом устройстве. Было бы глупо дублировать код доступа к sysfs в каждом проекте, который её использует. Поэтому libsysfs была выделена в отдельную подсистему.</p>
<span style="font-weight: bold; color: rgb(0, 0, 153);">udev</span>
<p align="justify">Чтобы приводить в действие правила именования устройств udev пользутся услугами namedev и libsysfs. udev вызывается каждый раз, когда ядро обращается к hotplug. Это происходит благодаря ссылке на udev в /etc/hotplug.d/default. Ядро сообщает hotplug массу интересной информации: что за устройство было добавлено (USB, PCI или ещё что-нибудь), а может оно было удалено, а не добавлено, где в sysfs находятся данные об устройстве, вызвавшем событие. Всё это передаётся в udev, который обращается в свою очередь к namedev, чтобы выяснить, какое имя присвоить устройству или просто удалить имя отключенного устройства (если action=remove). Т.о. содержимое /dev динамически изменяется на протяжении работы системы.</p>
По материалам Linux Symposium Версия 2. Публикуется с небольшими изменениями.<br />
<br />
Перевод (c) by Ваш покорный слуга, red f0xred_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0tag:blogger.com,1999:blog-3876463594612412036.post-9844890009159736392007-10-28T07:15:00.000-07:002007-10-28T07:25:59.412-07:00Toshiba Satellite A100-811: Bluetooth (solved)<p>Привет, мир! Я долго и упорно молчал, как румынский партизан и вот, наконец нашёл о чём написать :) Долго мне не удавалось заставить полноценно работать встроенный в ноутбук Bluetooth-адаптер. В конце-концов, задача была решена. Не скажу, что решение оказалось экстраординарным, но как это часто бывает, правильные мысли появляются пост-фактум.</p><p>Как писалось ранее, в некоторых моделях Toshiba Bluetooth включается через интерфейс HCI, в некоторых - через ACPI. Но стандартные драйвера toshiba и toshiba_acpi, входящие в ядро, для меня так и не заработали. Таким образом, первая проблема, с которой я здесь столкнулся, оказалось отсутствие драйвера. После некоторых поисков я наткнулся на <a href="http://omnibook.sourceforge.net">проект omnibook на sourceforge.net</a>. <i>Предполагается, что все необходимые дайверы были скомпилированы статически или в виде модулей. Конфигурация приблизительно такая:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>Configuration for 2.6 kernels
Networking --->
<br>
<*> Bluetooth subsystem support --->
<br>
--- Bluetooth subsystem support
<M> L2CAP protocol support
<M> SCO links support
<M> RFCOMM protocol support
[*] RFCOMM TTY support
<M> BNEP protocol support
[*] Multicast filter support
[*] Protocol filter support
<M> HIDP protocol support
<br>
Bluetooth device drivers --->
<M> HCI USB driver
[*] SCO (voice) support
<M> HCI UART driver
[*] UART (H4) protocol support
[*] BCSP protocol support
[*] Transmit CRC with every BCSP packet
<M> HCI BCM203x USB driver
<M> HCI BPA10x USB driver
<M> HCI BlueFRITZ! USB driver
(The four drivers below are for PCMCIA Bluetooth devices and will only
show up if you have also selected PCMCIA support in your kernel.)
<M> HCI DTL1 (PC Card) driver
<M> HCI BT3C (PC Card) driver
<M> HCI BlueCard (PC Card) driver
<M> HCI UART (PC Card) device driver
(The driver below is intended for HCI Emulation software.)
<M> HCI VHCI (Virtual HCI device) driver
<br>
(Move back three levels to Device Drives and then check if USB is
enabled. This is required if you use a Bluetooth dongle, which are mostly USB
based.)
USB support --->
<br>
<*> Support for Host-side USB
--- USB Host Controller Drivers
<M> EHCI HCD (USB 2.0) support
[ ] Full speed ISO transactions (EXPERIMENTAL)
[ ] Root Hub Transaction Translators (EXPERIMENTAL)
<*> OHCI HCD support
<*> UHCI HCD (most Intel and VIA) support
< > SL811HS HCD support</pre>
</span>
Правда, PCMCIA я у себя отключил.</i></p>
<p>Итак, качаем последнюю версию модуля с sourceforge.net.</p><span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>$ svn co https://omnibook.svn.sourceforge.net/svnroot/omnibook/trunk
...
$ mv trunk omnibook</pre></span><p>Собираем модуль:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># cd omnibook
# make -C /lib/modules/$(uname -r)/build modules SUBDIRS=$(pwd)</pre></span>
или просто<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># make; make install</pre></span>
:-) Теперь загружаем модуль с помощью modprobe omnibook.</p>
<p>В моём случае, правда, модуль не загрузился, т.к. моя модель не определилась как поддерживаемая. Модель определяется по данным из пула DMI (Desktop Management Interface - часть SMBIOS, хранящая данные о конфигурации платформы). Список официально поддерживаемых моделей можно найти <a href="http://omnibook.sourceforge.net/doku.php?id=laptops">здесь</a>. Но если даже Ваша модель не поддерживается официально, можно выяснить, какая из официально поддерживаемых моделей наиболее близка Вашей и загрузить модуль принудительно с параметром ectype=x (ectype - embedded controller type - тип встроенного контроллера). В моём случае ectype равен 12 (Toshiba Satellite M70). Если всё прошло удачно, в директории /proc появится поддиректория omnibook. Каждый файл в этой директории соответствует одной функции контроллера, кроме файлов version и dmi (версия драйвера и информация, полученная из пула DMI соответственно):<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre>f0x@devel0:~$ ls -la /proc/omnibook/
total 0
dr-xr-xr-x 2 root root 0 2007-10-23 23:50 .
dr-xr-xr-x 161 root root 0 2007-09-02 11:41 ..
-rw-r--r-- 1 root root 0 2007-10-23 23:50 blank
-rw-r--r-- 1 root root 0 2007-10-23 23:50 bluetooth
-rw-r--r-- 1 root root 0 2007-10-23 23:50 display
-r--r--r-- 1 root root 0 2007-10-23 23:50 dmi
-rw-r--r-- 1 root root 0 2007-10-23 23:50 lcd
-r--r--r-- 1 root root 0 2007-10-23 23:50 temperature
-rw-r--r-- 1 root root 0 2007-10-23 23:50 throttling
-r--r--r-- 1 root root 0 2007-10-23 23:50 version
-r--r--r-- 1 root root 0 2007-10-23 23:50 wifi</pre></span>За описанием функций контроллера Вы можете обратиться на <a href="http://omnibook.sourceforge.net/doku.php">http://omnibook.sourceforge.net/doku.php</a>. Нас же здесь больше всего интересует файл bluetooth. Файл содержит фразу "Bluetooth adapter is present and disabled." Т.е., по умолчанию bluetooth-адаптер отключен. Включить его можно простой записью единицы в файл:<span style="color: rgb(0, 0, 153); font-family: courier new;">
<pre>echo 1 > /proc/omnibook/bluetooth</pre></span>Для выключения, аналогично, записываем в файл "0".</p><p>Далее предполагается, что все необходимые пакеты для работы с bluetooth (bluez-* или метапакет bluetooth, к-й потянет за собой по зависимостям всё, что нужно и что ненужно тоже) установлены. Таким образом, следующий шаг - настройка всего, что нужно для работы с bluetooth. Прежде всего следует сконфигурировать hcid - демон, предоставляющий интерфейс к HCI - интерфейсу хост-контроллера. Ниже приводится моя конфигурация с комментариями:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># /etc/bluetooth/hcid.conf
#
# HCI daemon configuration file.
#
# HCId options
options {
# Automatically initialize new devices
autoinit yes;
# Security Manager mode
# none - Security manager disabled
# auto - Use local PIN for incoming connections
# user - Always ask user for a PIN
#
security auto;
# Pairing mode
# none - Pairing disabled
# multi - Allow pairing with already paired devices
# once - Pair once and deny successive attempts
pairing multi;
# Default PIN code for incoming connections
passkey "1234";
}
# Default settings for HCI devices
device {
# Local device name
# %d - device id
# %h - host name
name "%h";
# Local device class
class 0x3e0100;
# Default packet type
pkt_type DM1,DM3,DM5,DH1,DH3,DH5,HV1,HV2,HV3;
# Inquiry and Page scan
iscan enable;
pscan enable;
discovto 0;
# Default link mode
# none - no specific policy
# accept - always accept incoming connections
# master - become master on incoming connections,
# deny role switch on outgoing connections
lm accept;
# Default link policy
# none - no specific policy
# rswitch - allow role switch
# hold - allow hold mode
# sniff - allow sniff mode
# park - allow park mode
lp rswitch,hold,sniff,park;
}</pre></span>Немного по опциям. Секция <strong>options</strong> - настройки демона:<br><ol><li><strong>autoinit</strong> - автоматически инициализировать подсоединённые устройства (bluetooth-адаптеры)</li><li><strong>security</strong> - безопасность: none - не использовать PIN-код;<br><strong>auto</strong> - использовать для доступа к устройству, подсоединяемому через bluetooth локальный PIN по умолчанию, указанный в опции passkey;<br><strong>user</strong> - запрашивать PIN-код для установления соединения у пользователя.</li><li><strong>pairing</strong> - опции соединения: <strong>multi</strong> - разрешать соединения с устройствами, которые уже соединены;<br><strong>none</strong> - запретить соединения;<br><strong>once</strong> - допускать только одно соединение с помощью данного адаптера и игнорировать все последующие попытки установить новое соединение.</li><li><strong>passkey</strong> - PIN bluetooth-хоста по умолчанию. Он будет использоваться при добавлении Вашего компьютера другими bluetooth-устройствами в их списки устройств. Используйте любое число, которое Вам нравится.</li></ol>Секция <strong>device</strong> - свойства контроллера:<br><ol><li><strong>name</strong> - имя Вашего bluetooth-устройства (хоста). В моём случае bluetooth-хосту присваивается то же имя, что и имя Unix-хоста.</li><li><strong>class</strong> - класс устройства. Шестнадцатеричное число, определяющее класс bluetooth-устройства (хоста). С помощью битовой маски устанавливается тип хоста. За описанием битов обращайтесь к man hcid.conf или спецификации bluetooth. В моём случае средний байт 01 в 0x3e0100 обозначает, что мой хост - это компьютер, а не телефон, принтер или HID-устройство :-)</li><li><strong>pkt_type</strong> - типы пакетов. За разъяснениями также советую обращаться к спецификации Bluetooth или просто оставьте, как есть.</li><li><strong>iscan</strong> - разрешить другим bluetooth-устройствам обнаруживать Ваш хост при сканировании.</li><li><strong>pscan</strong> - разрешить другим устройствам устанавливать соединения с Вашим хостом, используя канал "page channel".</li><li><strong>discovto</strong> - время, на протяжении которого Ваш хост будет позволять обнаружить себя другому устройству. Устанавливается в секундах. 0 - устройство может быть обнаружено в любое время.</li><li><strong>lm</strong> - режим установки соединения: none - не определено;<br><strong>accept</strong> - всегда принимать входящие запросы на соединение;<br><strong>master</strong> - принимать входящие соединения в роли ведущего и запретить изменение этой роли при исходящих соединениях.</li><li><strong>lp</strong> - политика при установке соединения (касается пикосетей): <strong>none</strong> - не определено;<br><strong>rswitch</strong> - разрешить изменять роль (ведущий-ведомый);<br><strong>hold</strong> - эта политика необходима для синхронного обмена данными (нужно для беспроводной гарнитуры);<br><strong>sniff</strong> - позволить Вашему хосту пребывать в пикосети в определённые временные слоты. Когда устройство "отсутствует", оно может делать что-то другое, например, сканировать эфир, искать другие устройства;<br><strong>park</strong> - разрешить парковку хоста, т.е. позволять хосту переходить в энергосберегающий режим (сон). Устройство, входящее в пикосеть может "простаивать".</li></ol>В принципе, то что здесь написано - это мой перевод информации из man hcid.conf, здесь я не буду разъяснять все принципы работы bluetooth-устройств и сетей, в которые такие устройства могут объединяться. Всем любопытным советую обратиться к первоисточнику - спецификации по Bluetooth.</p><p>Далее, нам необходимо отредактировать файл настройки подсистемы bluetooth - rfcomm для нашего хост-контроллера. rfcomm - Radio Frequency Communication - радиочастотная связь, это подсистема, которая эмулирует последовательную линию RS232 поверх протокола L2CAP. В принципе, настройка подсистемы rfcomm опциональна. Но для устройства, которое Вы хотите использовать, как модем, для сотового телефона, настроить её будет желательно, чтобы не делать этого каждый раз вручную. Ниже приведён пример конфигурационного файла:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># /etc/bluetooth/rfcomm.conf
#
# RFCOMM configuration file.
#
rfcomm0 {
# # Automatically bind the device at startup
bind yes;
#
# # Bluetooth address of the device
device 00:0A:0B:0C:0D:0E;
#
# # RFCOMM channel for the connection
# channel 1;
#
# # Description of the connection
comment "SonyEricsson T630";
}</pre></span>Пояснения:<ol><li>rfcomm0 - первый последовательный интерфейс. Вообще-то, Вы можете сконфигурировать их столько, сколько захотите. Конфигурация каждого последовательного интерфейса начинается с его имени (rfcomm0, rfcomm1, rfcomm2, ...). Сами параметры интерфейса указываются в блоке между двумя фигурными скобками { и };</li><li><strong>bind</strong> - автоматически привязывать сокет для нашего устройства при его обнаружении (yes или no, в случае, если Вы не хотите, чтобы сокет связывался автоматически;</li><li><strong>device</strong> - MAC-адрес bluetooth-устройства (сотового телефона, например);</li><li><strong>channel</strong> - канал bluetooth, который будет использоваться для связи с устройством;</li><li><strong>comment</strong> - опциональный комментарий к интерфейсу.</li></ol>При указании МАС-адреса Вашего устройства, его сначала надо узнать. Для этого поднимем устройство hci и просканируем эфир:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># hciconfig hci0 up
# hciconfig piscan
# hcitool scan
Scanning ...
00:0E:0F:10:11:12 Red Quick Fox</pre></span>Здесь мы просто запускаем устройство hci0 с помощью утилиты hciconfig и сканируем эфир с помощью hcitool с параметром scan. 00:0E:0F:10:11:12 - это и есть нужный нам MAC-адрес. Утилита hciconfig предназначена для конфигурирования хост-контроллера hci. Если контроллеров несколько, то они имеют имена hci0, hci1 и т.д.. hcitool позволяет совершать некие вменяемые действия с помощью нашего хост-контроллера. Иными словами, она позволяет им управлять. Для списка агрументов обеих утилит см. man hciconfig и man hcitool. Используя утилиту l2ping и МАС-адрес в качестве её аргумента, можно послать пинга телефону :-)</p><p>Следующая стадия конфигурирования bluetooth - редактирование ещё одного файла. В debian-based дистрибутивах linux это /etc/default/bluetooth. По существу, этот файл нужен только стартовому скрипту bluetooth из /etc/init.d. Сложного там ничего нет. Всё, что от Вас требуется, указать, должны ли сервисы bluetooth стартовать при загрузке системы и если да, то какие. Приведём пример файла /etc/default/bluetooth:<span style="color: rgb(0, 0, 153); font-family: courier new;"><pre># Defaults for bluez-utils
# This file supersedes /etc/default/bluez-pan. If
# that exists on your system, you should use this
# file instead and remove the old one. Until you
# do so, the contents of this file will be ignored.
# start bluetooth on boot?
# compatibility note: If this variable is not found bluetooth will
# start
BLUETOOTH_ENABLED=1
############ DUND
#
# Run dund -- this allows ppp logins. 1 for enabled, 0 for disabled.
DUND_ENABLED=1
# Arguments to dund: defaults to acting as a server
DUND_OPTIONS="--listen --persist"
# Run dund --help to see the full array of options.
# Here are some examples:
#
# Connect to any nearby host offering access
# DUND_OPTIONS="--search"
#
# Connect to host 00:11:22:33:44:55
# DUND_OPTIONS="--connect 00:11:22:33:44:55"
#
# Listen on channel 3
# DUND_OPTIONS="--listen --channel 3"
# Special consideration is needed for certain devices. Microsoft
# users see the --msdun option. Ericsson P800 users will need to
# listen on channel 3 and also run 'sdptool add --channel=3 SP'
############ SDPTOOL
# this variable controls the options passed to sdptool on boot, useful if you
# need to setup sdpd on boot.
# options are ;-separated, i.e. for every ; an sdptool instance will be
# launched
#
# examples:
# SDPTOOL_OPTIONS="add --channel=3 SP" # ericsson P800 serial profile
# SDPTOOL_OPTIONS="add --channel=8 OPUSH ; add --channel=9 FTRN" # motorola
# # object push and file transfer
SDPTOOL_OPTIONS=""
</pre></span></p><p>Здесь переменная BLUETOOTH_ENABLED=1 определяет, что при загрузке системы должны стартовать и сервисы bluetooth. Если переменная установлена в 0, поддержка bluetooth не включается. Если переменная не найдена, то по умолчанию сервисы загружаются. DUND_ENABLED=1 определяет, что должен быть запущен демон Dial-up-доступа. Ну, вот практически и всё. В переменной SDPTOOL_OPTIONS определяется, какие сервисы должен предоставлять наш компьютер другим bluetooth-устройствам. Список сервисов можно пополнить и вне этого файла с помощью sdptool (см. man sdptool). В общем и в целом Unix-box теперь готов к работе с bluetooth-устройствами. Можете по bluetooth зайти на сотовый телефон, настроить пикосеть, работающую через BNEP или ещё что-нибудь. Про HID я не писал, ибо не имею таковых девайсов. Про настройку пикосети на bluetooth Вы сможете узнать сами.</p><p>Что касаемо телефонов, было замечено одно занятное обстоятельство: PIN, указанный в конфигурационном файле hcid.conf внаглую игнорируется с новыми телефонами (т.е., с теми, с которыми мы раньше не работали). В качестве противоядия можно использовать следующее: создать маленький скрипт, который просто выводит с помощью echo PIN-код - echo "PIN:XXXX", где ХХХХ - Ваш PIN. Допустим, этот скрипт будет называться helper. Далее, можно запустить passkey-agent --default ./helper. По идее, агент должен запускаться автоматически для каждой пользовательской сессии, но как показывает опыт, он этого почему-то не делает. Можно самостоятельно запихать passkey-agent в какой-нибудь скрипт. Ну, вот, собственно, и всё. Чтобы использовать сотовый телефон для Dial-up доступа в интернет в качестве GPRS-модема, укажите в качестве устройства модема /dev/rfcomm0 в конфиге Вашей звонилки, настройте доступ согласно информации Вашего оператора. А дальше - всё как всегда.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com2tag:blogger.com,1999:blog-3876463594612412036.post-27935928155963374492007-08-28T15:50:00.000-07:002007-08-28T16:10:18.475-07:00Селим OpenSolaris рядом с Linux: загрузка одним bootloader'ом или история одной глупости<p>Уже довольно давно, может с месяца с 4, у меня валялся набор из 2 дисков OpenSolaris 10 Starter Kit. Так как солярой я интересовался и раньше и эпизодически ею пользовался (Sun Solaris 9 x86), то решил наконец попробовать и этот продукт. На диске есть 3 дистрибутива в редакции community: Nexenta, Schilix и Belenix. Опробовав последовательно все 3 образа (они загружаются с DVD-диска как Live без необходимоси установки), я остановил свой выбор на последнем, как на наиболее соответствующем моим требованиям дистре (gcc в комплекте и всё необходимое для разработки - библиотеки, плюс, конечно, zfs, Solaris zones, DTrace и прочее).</p><p>Кратко о самом дистре Belenix: довольно симпатичный дистр, в качестве рабочей графической среды предоставляется на выбор KDE 3.5 или Xfce, коим я и пользуюсь. По умолчанию графика не запускается. В смысле, она запускается на полуавтомате при загрузке LiveCD, но не в уже установленном солярисе, где по умолчанию пускать Xfce нужно явно, руками (хотя, никто не мешает Вам настроить этот момент под Ваши нужды). Порадовало, что без проблем определяются интегрированное аудио и eth-адаптер на nForce430, с дисковым контроллером проблем также никаких (у меня 2 SATA-II винчестера). В отличие от Nexenta здесь ничего не валится в коры буквально на ровном месте. Установка проходит без проблем и в принципе, процедура довольна тривиальна, поэтому на ней я останавливаься не буду. Единственное, что хотелось бы отметить, что инсталлятор как был во всех версиях Solaris, что мне попадались - текстовый, таковым остался и здесь. Однако, я не считаю это недостатком. Но вместе с тем, опций здесь лишь самый минимум - думаю, можно было бы сделать и побольше, хотя бы на предмет того, какой софт нужно ставить, а какой нет.</p><p>Итак, я выкроил свободное место на втором винчестере и поставил OS туда. Непредвиденные трудности начались, когда я попытался при перезагрузке получить мой свежеустановленный Belenix. Суть проблемы в следующем: до этого у меня уже был установлен Debian GNU/Linux 'Etch' и всё, что обреталось у меня на компьютере, грузилось его grub'ом. Честно говоря, мне не было особого резона использовать в качестве общесистемного загрузчика Solaris'овский grub, потому как всё же большую часть времени я провожу в Linux и такая схема была бы не очень удобна. Но Linux'овый grub понятия не имеет о zfs или ufs2 (хотя, последнее по идее должен уметь). В конце концов я уже готов был пойти на крайность и сделать общесистемным загрузчиком Solaris'овский grub. Перезагрузившись с Belenix LiveCD, вызвал installgrub со следующими параметрами:<span style="color: rgb(0, 0, 153); font-family: courier new;">
<pre>
# grubinstall -m /mnt/solaris0/boot/grub/stage1 /mnt/solaris0/boot/grub/stage2 /dev/rdsk/c2d0s0
</pre>
</span>В общем, здесь всё довольно self-explanatory: указываем файл для первой стадии загрузки grub (сектор, внедряемый в MBR), образ для второй стадии и слайс, на котором стоит Solaris (честно говоря, я до сих пор путаюсь в схемах BSD и Solaris, но если я не ошибаюсь, то это должен бы быть лейбл, а не слайс, ведь слайс - это раздел в терминологии DOS, однако по имени видно, что у нас контроллер 2, первый диск на 2-м контроллере и 1-й слайс на этом диске - так или иначе, но эта схема совершенно отлична от той, что используется в Linux и не совсем похожа на Free- или NetBSD'шную. ИМХО в FreeBSD имена устройств выглядят логичнее и нагляднее, чем в Solaris). Ключ -m здесь просто добавляет интерактивности. Если Вы не накачались пивом и прекрасно понимаете, что Вы собираетесь сделать, то им можно и пренебречь :) /mnt/solaris0 - подмонтированная ФС с уже установленной на диск солярой - мы будем брать необходимые файлы с этой ФС. Так вот, каково было моё разочарование, когда я обнаружил, что первый загрузочный сектор оказался не в MBR первого винчестера, как я ожидал, а в mboot'e второго. Указать installgrub'у явным образом, куда необходимо воткнуть загрузочный сектор не представлялось возможным.</p><p>В общем, ситуация вышла достаточно комичной. Linux'овый grub в упор не воспринимал Solaris'овскую ufs2 и о том, чтобы он мог грузить соляровое ядро непосредственно не могло быть и речи, а с другой стороны соляровый загрузчки упорно становился в mboot второго диска и, следовательно, пролетал при загрузке.</p><p>Решение было ясно как день и простое, как карандаш. Честно говоря, если бы не лень, можно было бы не морочить себе голову. Дело в том, что я пытался редактировать меню загрузки на лету, добавляя<br>rootnoverify (hd1)<br>chainloader +1<br>Но по не совсем понятной мне причине Linux'овый grub ругался кодом ошибки 27 и как резанный кричал, что не знает такой команды. Редактирование menu.lst для Linux'ового grub'а расставило всё по своим местам (хотя и странно, почему такого результата не удавалось добиться редактированием параметров загрузки на лету - ограничение внедряемого кода grub-шелла для экономии, чтобы по возможности сократить число секторов, которые необходимо внедрить?). Теперь и солярис и линукс грузятся одним загрузчиком - Linux'овым, с той поправкой, что солярис грузится не непосредственно, а через встроенный в цепь собственный загрузчик, который живёт в MBR второго диска.</p><p>Результат: в MBR 1-го диска установлен Linux grub, а в файл menu.lst для загрузки зацеплённого солярового grub'a, что установлен нами ранее в MBR 2-го винчестера installgrub'ом, были добавлены строки:<span style="color: rgb(0, 0, 153); font-family: courier new;">
<pre>
title Belenix 32-bit
rootnoverify (hd1) # Используем в качестве корня MBR 2-го диска (hd1), и не пытаемся ничего монтировать - не получится, т.к. там просто нет ФС и нечего монтировать :)
chainloader +1 # Встраиваем в цепь вторичный бутлоадер, на к-й ссылается MBR 2-го диска
</pre>
</span>Есть ещё одно обстоятельство. Файл menu.lst для солярового grub'a также нуждается в небольшой правке, которую лучше сделать именно в файле, чем каждый раз корректировать на лету команды загрузки. А именно, по непонятному недоразумению, которое уже описывалось ранее, соляровый инсталлятор grub'a, а вместе с ними вдогонку и сам grub почему-то считают, что они установлены на 1-м диске, а не на 2-м, как у <i>меня</i> обстоит дело в реальности. В результате, в /boot/grub/menu.lst для соляриса имеем такие сроки:<span style="color: rgb(0, 0, 153); font-family: courier new;">
<pre>
title Belenix (32Bit, ACPI)
root (hd0,3,a) # Ошибка: Корневая ФС, на которой находятся файлы образа ядра и загрузочный архив находится на втором диске, а не на первом (первое число должно быть 1, а не 0)!
# Далее - всё в полном порядке :)
kernel /platform/i86pc/multiboot kernel/unix
module /platform/i86pc/boot_archive
</pre>
</span>Строка root нуждается в следующей правке: 0 необходимо заменить на 1 для второго диска, а в остальном всё правильно в моём случае - слайс 3, лейбл - а. С учётом сказанного, она должна выглядеть так: <b>root (hd1,3,a)</b>. Хотя, если у Вас OS установлен на 1-м винчестере, то эта правка Вам не нужна.</p><p>Вместо Afterword: как и в Linux'e в OpenSolaris есть определённые ограничения для корневой ФС или, по крайней мере, /boot. Так, в Linux нет возможности внедрить секторы со второй стадией загрузки grub в раздел, который отформатирован в xfs (просто в xfs исторически не предусмотрена такая возможность, т.к. эта ФС была разработана для SGI IRIX и изначально к Linux никакого отношения не имела. Итог состоит в том, что в голове xfs-раздела нет необходимых 64 Кб для внедрения grub-секторов. Поэтому или всю корневую ФС или, по крайней мере, /boot нужно держать на ФС, более традиционной для Linux - ReiserFS, Ext2/3 и т.п.) Аналогично, grub на самом деле не имеет понятия о пулах zfs (на самом деле, если не ошибаюсь), но тот, что имеется в OS прекрасно работает по крайней мере с ufs2. Поэтому, выход здесь, как и в случае с Linux - держать корневую ФС на ufs2, а уже остальное, типа /home - в пуле на zfs.</p><p>PS: небольшая попроавка к примеру с xfs и grub. Честно говоря, я не уверен, что информация актуальна, но насколько можно судить сейчас по некоторым данным grub прекрасно работает и с корневой ФС на xfs. Но так как я не уверен, то текст оставил без исправлений. Если я всё-таки ошибся, то извините за неточность - мы все учимся, и не редко на ошибках и с их же помощью :)</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com1tag:blogger.com,1999:blog-3876463594612412036.post-80049213746661522852007-06-09T07:08:00.000-07:002007-06-09T07:15:37.643-07:00perl-proxy: Немного юмора<p>В связи с работой над одним левым заказом мне пришлось заинтересоваться перлопроксями и вот, что из этого получилось:<span style="color: rgb(0, 0, 153); font-family: courier new;">
<pre>
#!/usr/bin/perl -Tw
use strict;
$ENV{PATH} = join ":", qw(/usr/ucb /bin /usr/bin);
$|++;
my $VERSION_ID = q$Id: proxy,v 1.21 1998/xx/xx xx:xx:xx merlyn Exp $;
my $VERSION = (qw$Revision: 1.21 $ )[-1];
## Copyright (c) 1996, 1998 by Randal L. Schwartz
## This program is free software; you can redistribute it
## and/or modify it under the same terms as Perl itself.
### debug management
sub prefix {
my $now = localtime;
join "", map { "[$now] [${$}] $_\n" } split /\n/, join "", @_;
}
$SIG{__WARN__} = sub { warn prefix @_ };
$SIG{__DIE__} = sub { die prefix @_ };
&setup_signals();
### logging flags
my $LOG_PROC = 1; # begin/end of processes
my $LOG_TRAN = 1; # begin/end of each transaction
my $LOG_REQ_HEAD = 0; # detailed header of each request
my $LOG_REQ_BODY = 0; # header and body of each request
my $LOG_RES_HEAD = 0; # detailed header of each response
my $LOG_RES_BODY = 0; # header and body of each response
### configuration
my $HOST = 'WWW.XXX.YYY.ZZZ';
my $PORT = 3128; # pick next available user-port
my $SLAVE_COUNT = 8; # how many slaves to fork
my $MAX_PER_SLAVE = 20; # how many transactions per slave
### main
warn("running version ", $VERSION);
&main();
exit 0;
### subs
sub main { # return void
use HTTP::Daemon;
my %kids;
my $master = HTTP::Daemon->new(LocalPort => $PORT, LocalAddr => $HOST) or die "Cannot create master: $!";
warn("master is ", $master->url);
## fork the right number of children
for (1..$SLAVE_COUNT) {
$kids{&fork_a_slave($master)} = "slave";
}
{ # forever:
my $pid = wait;
my $was = delete ($kids{$pid}) || "?unknown?";
warn("child $pid ($was) terminated status $?") if $LOG_PROC;
if ($was eq "slave") { # oops, lost a slave
sleep 1; # don't replace it right away
#(avoid thrash)
$kids{&fork_a_slave($master)} = "slave";
}
} continue { redo }; # semicolon for cperl-mode
}
sub setup_signals { # return void
setpgrp; # I *am* the leader
$SIG{HUP} = $SIG{INT} = $SIG{TERM} = sub {
my $sig = shift;
$SIG{$sig} = 'IGNORE';
kill $sig, 0; # death to all-comers
die "killed by $sig";
};
}
sub fork_a_slave { # return int (pid)
my $master = shift; # HTTP::Daemon
my $pid;
defined ($pid = fork) or die "Cannot fork: $!";
&child_does($master) unless $pid;
$pid;
}
sub child_does { # return void
my $master = shift; # HTTP::Daemon
my $did = 0; # processed count
warn("child started") if $LOG_PROC;
{
flock($master, 2); # LOCK_EX
warn("child has lock") if $LOG_TRAN;
my $slave = $master->accept or die "accept: $!";
warn("child releasing lock") if $LOG_TRAN;
flock($master, 8); # LOCK_UN
my @start_times = (times, time);
$slave->autoflush(1);
warn("connect from ", $slave->peerhost) if $LOG_TRAN;
&handle_one_connection($slave); # closes $slave at right time
if ($LOG_TRAN) {
my @finish_times = (times, time);
for (@finish_times) {
$_ -= shift @start_times; # crude, but effective
}
warn(sprintf "times: %.2f %.2f %.2f %.2f %d\n", @finish_times);
}
} continue { redo if ++$did < $MAX_PER_SLAVE };
warn("child terminating") if $LOG_PROC;
exit 0;
}
sub handle_one_connection { # return void
use HTTP::Request;
my $handle = shift; # HTTP::Daemon::ClientConn
my $request = $handle->get_request;
print $request;
defined($request) or die "bad request"; # XXX
my $response = &fetch_request($request);
warn("response: <<<\n", $response->headers_as_string, "\n>>>")
if $LOG_RES_HEAD and not $LOG_RES_BODY;
warn("response: <<<\n", $response->as_string, "\n>>>")
if $LOG_RES_BODY;
$handle->send_response($response);
close $handle;
}
sub fetch_request { # return HTTP::Response
use HTTP::Response;
use URI::URL;
my $request = shift; # HTTP::Request
## XXX
print "Request was: " . $request->as_string;
## XXXX needs policy here
my $url = URI::URL->new($request->url);
if ($url->scheme !~ /^(https?|gopher|ftp)$/) {
my $res = HTTP::Response->new(403, "Forbidden");
$res->content("bad scheme: @{[$url->scheme]}\n");
$res;
} elsif (not $url->rel->netloc) {
my $res = HTTP::Response->new(403, "Forbidden");
$res->content("relative URL not permitted\n");
$res;
} else {
## validated request, get it!
warn("processing url is $url") if $LOG_TRAN;
&fetch_validated_request($request);
}
}
BEGIN { # local static block
my $agent; # LWP::UserAgent
sub fetch_validated_request { # return HTTP::Response
my $request = shift; # HTTP::Request
$agent ||= do {
use LWP::UserAgent;
my $agent = LWP::UserAgent->new;
$agent->agent("proxy/$VERSION " . $agent->agent);
$agent->env_proxy;
$agent;
};
warn("fetch: <<<\n", $request->headers_as_string, "\n>>>")
if $LOG_REQ_HEAD and not $LOG_REQ_BODY;
warn("fetch: <<<\n", $request->as_string, "\n>>>")
if $LOG_REQ_BODY;
my $response = $agent->simple_request($request);
if ($response->is_success and $response->content_type =~ /text\/(plain|html)/ and not ($response->content_encoding || "") =~ /\S/ and ($request->header("accept-encoding") || "") =~ /gzip/) {
require Compress::Zlib;
my $content = $response->content;
my $new_content = Compress::Zlib::memGzip($content);
if (defined $new_content) {
$response->content($new_content);
$response->content_length(length $new_content);
$response->content_encoding("gzip");
warn("gzipping content from " . (length $content) . " to " . (length $new_content)) if $LOG_TRAN;
}
}
$response;
}
}</pre>
</span></p><p>Немного разоблачений:<ol>
<li>Код не мой, как видно по копирайту - я лишь подправил кое-что в области URI::URL, чтоб оно работало и добавил print $request->as_string для отладки.</li>
<li>Переменную $HOST устанавливаем в адрес хоста, на котором крутится прокси, а $PORT присваиваем номер порта, который слушает наш прокси.</li>
<li>Настраиваем клиент на использование прокси - указываем адрес хоста и порт $HOST:$PORT</li>
<li>Скрипт при запуске плодит pre-forked потомков, которые слушают порт $PORT</li>
<li>При поступлении запроса на соединение на указанный порт с помощью манипуляций объектами HTTP::Request, HTTP::Response и LWP::UserAgent перебрасываем запрос на сервер-рецепиент.</li></ol></p>
<p>Вот так вкратце. А впрочем, из кода довольно прозрачно всё видно. Всё гениальное просто :) Можно запустить эту игрушку и шарить т.о. одно dial-up соединение для доступа к web (чем я сейчас и занимаюсь), не трогая NAT.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com3tag:blogger.com,1999:blog-3876463594612412036.post-42885374449656201112007-06-02T11:00:00.000-07:002007-06-02T11:05:06.684-07:00Toshiba Satellite A100-811: Ubuntu & power management<p>Странное дело, но после апгрейда до KUbuntu 7.04 Feisty Fawn на ноутбуке сломался hibernate suspend-to-disk. Правка /etc/hibernate/common.conf ничего не давала. Процесс сброса образа RAM на диск, кажется, шёл вполне нормально, но после возобновления (resume) X оказывался запорот. Система не висит - в неё даже можно залогиниться по ssh, но работа непосредственно на ноутбуке невозможна. Итак, немного подумав, я открыл для себя uswsusp и входящий сюда s2disk. Однако, и тут не всё было гладко. Одно время s2disk работал для меня вполне прекрасно, даже с некоторыми особенностями. Так, например, после слива образа на диск и закрытия крышки лаптопа при её отркывании система включалась сама, без нажатия кнопки power и linux возобновлялся. Но, позже появилась проблема. При открытии крышки ноутбук включался, появлялся сплэш и... всё. Сплэш замерзал на одном месте и система не возобновлялась. Немного подумав, я полез шерстить по конфигам. Итак, найдя файл /etc/uswsusp.conf, я сделал такую правку:<span style="color: rgb(0, 0, 153); font-family: courier new;">
<pre>
# /etc/uswsusp.conf(8) -- Configuration file for s2disk/s2both
resume device = /dev/sda5
compress = y
early writeout = y
image size = 512000000
compute checksum = y
RSA key file = /etc/uswsusp.key
shutdown method = platform
splash = y
</pre>
</span>
Поясню: до этого параметр splash в конфиге не был определён вообще, а image size был равен 236Мб с копейками. Как видно, я явно установил splash = y, для фидбэка в процессе слива образа RAM на диск и в процессе его считывания с диска, а image size в пол-гигабайта (объём RAM на моём ноутбуке - 512Мб). Как ни странно, но после этого всё заработало. Скажу честно, я исходил из ни на чём не обоснованного предположения, что всё дело именно в том, что на диск сливается не весь образ памяти. Это было довольно интуитивное решение. Экспериментировать со значением 0 я не стал, но в man uswsusp.conf написано, что при нулевом значении s2disk будет пытаться сделать образ как можно меньше, сохраняя только самый необходимый минимум.</p>
<p>Теперь s2disk работал. И работал прекрасно. Единственное, чего мне ещё не хватало, это чтобы он срабатывал автоматически при критическом уровне батареи (такая настройка у меня была и ранее - в целях предотвращения потерь данных. Вполне вероятна такая ситуация, когда Вы отойдёте, задержитесь где-то, а тем временем аккумулятор благополучно иссякнет и...). К сожалению, отрабатывало оно так же, как и усыпление через диалог KDE "Завершить сеанс" -> "Спящий режим", или точнее сказать, не работало вовсе. Linux просто гасил экран, блокировал сеанс KDE, а засыпать даже не думал. Я пользуюсь KLaptop. Так сложилось традиционно, да и просто, он субъективно нравится мне больше всяких там KPowersave. Там вполне можно было прописать, какую команду следует выполнить при критически малом уровне аккумулятора, но мне хотелось универсального решения, ведь для кнопки в KDE'шном завершении сеанса такой параметр не прописан. Механизм работы здесь однако был один. Это я знал по опыту - по одинаковому поведению при попытке усыпить ноутбук через KLaptop и "Завершение сеанса". Немного пошарив по google я нашёл решение. Оказалось, что hibernation здесь срабатывает через hal. Для того, чтобы исправить ситуацию, необходимо было заменить модуль hal, который отвечал за усыпление по требованию клиента. Этот модуль представляет собой нечто иное, как скрипт, лежащий в /usr/lib/halscripts/linux - hal-system-power-hibernate-linux. Моей целью было заставить этот скрипт отрабатывать через s2disk. Первоначально скрипт выглядёл следующим образом:<span style="color: rgb(0, 0, 153); font-family: courier new;">
<pre>
#!/bin/sh
POWERSAVED_SUSPEND2DISK="dbus-send --system --dest=com.novell.powersave \
--print-reply /com/novell/powersave \
com.novell.powersave.action.SuspendToDisk"
unsupported() {
echo org.freedesktop.Hal.Device.SystemPowerManagement.NotSupported >&2
echo No hibernate script found >&2
exit 1
}
#SuSE and ALTLinux only support powersave
if [ -f /etc/altlinux-release ] || [ -f "/etc/SuSE-release" ] ; then
if [ -x /usr/bin/powersave ] ; then
$POWERSAVED_SUSPEND2DISK
RET=$?
else
unsupported
fi
#Mandriva support suspend-scripts
elif [ -f /etc/mandriva-release ] ; then
if [ -x /usr/sbin/pmsuspend ] ; then
/usr/sbin/pmsuspend disk
RET=$?
else
unsupported
fi
#RedHat/Fedora only support pm-utils
elif [ -f /etc/redhat-release ] || [ -f /etc/fedora-release ] ; then
if [ -x /usr/sbin/pm-hibernate ] ; then
/usr/sbin/pm-hibernate
RET=$?
else
unsupported
fi
#Other distros just need to have *any* tools installed
else
if [ -x "/usr/bin/powersave" ] ; then
$POWERSAVED_SUSPEND2DISK
RET=$?
elif [ -x "/usr/sbin/pmi" ] ; then
/usr/sbin/pmi action hibernate force
RET=$?
elif [ -x "/usr/sbin/pm-hibernate" ] ; then
/usr/sbin/pm-hibernate
RET=$?
elif [ -x "/usr/sbin/hibernate" ] ; then
# Suspend2 tools installed
/usr/sbin/hibernate --force
RET=$?
elif [ -x "/sbin/s2disk" ] ; then
# uswsusp tools installed
/sbin/s2disk
RET=$?
elif [ -x "/etc/acpi/hibernate.sh" ] ; then
# acpi-support installed
/etc/acpi/hibernate.sh force
RET=$?
elif [ -w "/sys/power/state" ] ; then
# Use the raw kernel sysfs interface
echo "disk" > /sys/power/state
RET=$?
else
unsupported
fi
fi
#Refresh devices as a resume can do funny things
for type in button battery ac_adapter
do
devices=`hal-find-by-capability --capability $type`
for device in $devices
do
dbus-send --system --print-reply --dest=org.freedesktop.Hal \
$device org.freedesktop.Hal.Device.Rescan
done
done
exit $RET</pre></span>
</p>
<p>Из приведённого листинга видно, что сперва скрипт пытается определить дистрибутив, а далее действует исходя из реалий данного дистрибутива. В моём случае hal пытался отработать через pmi - абстактный интерфейс управления энергосбережением. Мне же нужно было, чтобы первым и последним срабатывал s2disk. После исправления скрипт обрёл такой вид:<span style="color: rgb(0, 0, 153); font-family: courier new;">
<pre>
#!/bin/sh
[ SKIPPED ]
#Other distros just need to have *any* tools installed
else
if [ -x "/sbin/s2disk" ] ; then
# uswsusp tools installed
/sbin/s2disk
RET=$?
elif [ -x "/usr/sbin/pmi" ] ; then
/usr/sbin/pmi action hibernate force
RET=$?
elif [ -x "/usr/bin/powersave" ] ; then
$POWERSAVED_SUSPEND2DISK
RET=$?
elif [ -x "/usr/sbin/pm-hibernate" ] ; then
/usr/sbin/pm-hibernate
RET=$?
elif [ -x "/usr/sbin/hibernate" ] ; then
# Suspend2 tools installed
/usr/sbin/hibernate --force
RET=$?
elif [ -x "/etc/acpi/hibernate.sh" ] ; then
# acpi-support installed
/etc/acpi/hibernate.sh force
RET=$?
elif [ -w "/sys/power/state" ] ; then
# Use the raw kernel sysfs interface
echo "disk" > /sys/power/state
RET=$?
else
unsupported
fi
fi
[ SKIPPED ]
</pre>
</span>После этого и KLaptop и "Завершение сеанса" работают безупречно.</p>
<p>Кстати, режим suspend-to-ram у меня работал хорошо из коробки (после апгрейда до Feisty Fawn). s2ram напротив работать не хочет, ссылаясь на то, что модель ноута ему не знакома. Поэтому мои настройки энергосбережения закончились доведением до ума режима suspend-to-disk a.k.a. hibernation.</p>
<p>"Вот так мы победили сырость!" (с)
Однако, есть ещё над чем поработать. Так, например, мне до сих пор не удалось заставить работать русский в консоли. Самое странное, что на десктопе с установленным Debian 4.0 Etch всё прекрасно работает (console-cyrillic не установлен). При такой же конфигурации на лаптопе оно не работает. Странность в том, что из коробки путём лишь небольших манипуляций после замены дистрибутивного ядра на собственное русский текст в UTF8 отображается в консоли без проблем, но нет возможности набирать русский текст. Вместо него не набирается вообще ничего. Одним словом, проблема всё ещё требует решения. В Edgy Eft в этом плане всё было отлично. Примечательно, что такие проблемы после перехода на Feisty Fawn наблюдаются не только у меня.</p>
<p>Вторая проблема более странная. С ней я пока не разбирался вплотную. Я наконец решил заставить работать встроенный модем. Поставил sl-modem-daemon, sl-modem-source для компиляции модулей под моё кастом-билт ядро. Но тут я столкнулся с тем, что sl-modem-source на уровне исходного кода не совместим с новыми ядрами. К примеру, в коде kernel-ver.c делается попытка подключить заголовок linux/version.h чтобы использовать макрос UTS_RELEASE, который в 2.6.20 (и не только в нём) уже определяется в другом файле - linux/utsrelease.h. Можно, конечно, и вручную подправить сырцы драйвера... Кстати, смешной момент, связанный с установкой драйвера. Если сказать apt-get install sl-modem-source, то в Вашем /usr/src появится тарбол sl-modem.tar.bz2 - это и есть сырцы драйвера. Далее, пытаемся ставить его с помощью module-assistant (хех, Debian-way): module-assistant prepare sl-modem; module-assistant install sl-modem и тут происходит ошибка. По вышеупомянутой причине. Правим сырцы, устраняем явные противоречия и снова пускаем m-a install sl-modem и... снова ошибка! В том же месте! :) Лезем в исходники и видим, наших изменений там нет! Так что, мораль в том, что если Вы собираетесь править сырцы sl-modem вручную, то Вам нужно будет из подправленных сырцов создать такой же тарбол с тем же именем, что и оригинал и заменить оригинал. Правда, я пошёл по другому пути - просто выкачал sl-modem-source из пула Debian Sid. Всё собралось без сучка и задоринки, но slmodemd всё равно не видит модули, хотя они и установлены в /lib/modules/`uname -r`/misc. Говорит: FATAL: Module slamr not found. Странно, короче. Можно было бы обойтись без модулей, используя alsa, но это, ИМХО - крайний случай.</p>red_f0xhttp://www.blogger.com/profile/02367659412016964697noreply@blogger.com0