Журнал LinuxFormat - перейти на главную

LXF158:ARM и Android:Про­грам­ми­ро­ва­ние

Материал из Linuxformat
Перейти к: навигация, поиск


» Ваши программные наработки не пропадут даром

ARM и Android: Про­грам­ми­ро­ва­ние

Часть 4: Ан­д­рей Бо­ров­ский бе­рет­ся за про­грам­ми­ро­ва­ние на ас­семб­ле­ре ARM и пре­неб­ре­га­ет пра­ва­ми root.

Наш эксперт

Ан­д­рей Бо­ров­ский В 14 лет сломал школьную локальную сеть. И это оказалось только началом.

В этой, за­вер­шаю­щей час­ти мы при­коснем­ся к ув­ле­ка­тель­но­му ми­ру про­грам­ми­ро­вания на ас­семб­ле­ре ARM, а так­же рас­смот­рим про­грам­му Android, ко­то­рая по­зво­лит за­пускать про­грам­мы Linux на уст­рой­ст­вах Android, не имею­щих ре­жи­ма root.

На­чи­ная эту се­рию ста­тей, я пи­сал, что од­ной из при­чин для раз­ра­бот­ки про­грамм Linux под Android мо­жет стать стрем­ление по­зна­ко­мить­ся с осо­бен­но­стя­ми про­цес­со­ров ARM в сравнитель­но ком­форт­ных усло­ви­ях. Имен­но этим мы те­перь и займ­ем­ся.

NEON!= OFF

Кон­цеп­ция, по­ло­жен­ная в осно­ву про­цес­со­ров ARM, из­на­чаль­но вы­гля­де­ла очень кра­си­во. В то вре­мя как про­цес­со­ры Intel об­рас­та­ли раз­лич­ны­ми косты­ля­ми, при­зван­ны­ми обес­пе­чить со­вмес­ти­мость с пре­ды­ду­щи­ми мо­де­ля­ми и кон­ку­рен­та­ми (AMD), про­цес­со­ры ARM со­хра­ня­ли ком­пакт­ную и эле­гант­ную струк­ту­ру. И хо­тя Intel пре­восхо­дил ARM в про­из­во­ди­тель­но­сти, ARM с его низ­ки­ми за­тра­та­ми энер­гии и пас­сив­ным ох­ла­ж­дением на­шел свою нишу в ми­ре встраи­вае­мых уст­ройств. Вто­рое ро­ж­дение ARM пе­ре­жил в нынеш­нюю эпо­ху смарт­фо­нов и дру­гих ин­тел­лек­ту­аль­ных мо­биль­ных уст­ройств, ко­то­рые тре­бу­ют со­че­тания вы­чис­ли­тель­ной мо­щи ПК и низ­ко­го энер­го­по­треб­ления.

Но за все при­хо­дит­ся пла­тить. Со­вре­мен­ные мо­биль­ные уст­рой­ст­ва на­сы­ще­ны гра­фи­кой, ви­део и зву­ком, а для об­ра­бот­ки все­го это­го в ре­жи­ме ре­аль­но­го вре­мени тре­бу­ют­ся спе­ци­аль­ные на­бо­ры команд. И про­цес­со­ры ARM, рас­став­шись с из­на­чаль­ной про­сто­той, об­за­ве­лись та­ки­ми на­бо­ра­ми. При­чем, как это час­то бы­ва­ет в по­доб­ных слу­ча­ях, вве­ден­ные сна­ча­ла на­бо­ры до­полнитель­ных команд ока­за­лись недоста­точ­но удоб­ны­ми и функ­цио­наль­ны­ми, а отказать­ся от них бы­ло уже нель­зя, так как они ис­поль­зо­ва­лись во мно­гих при­ло­жениях. В ре­зуль­та­те ар­хи­тек­ту­ра ARM, как и Intel, об­ла­да­ет те­перь несколь­ки­ми на­бо­ра­ми команд для ра­бо­ты с чис­ла­ми с пла­ваю­щей точ­кой и мас­сива­ми дан­ных, ко­то­рые час­тич­но дуб­ли­ру­ют функ­ции друг дру­га.

Ес­ли вы от­крое­те файл /system/proc/cpuinfo (на нерутованном де­вай­се со­дер­жи­мое это­го фай­ла мож­но по­лу­чить, на­при­мер, с по­мо­щью про­грам­мы Droid System Suite), то уви­ди­те пе­ре­чень рас­ши­рений (fastmult, vfp и ду­гие), под­дер­жи­вае­мых ва­шим про­цес­со­ром. Эти рас­ши­рения по­хо­жи на рас­ши­рения Intel (mmx, sse и т. д.), но, ра­зу­ме­ет­ся, не то­ж­де­ст­вен­ны им. Рас­смат­ри­вать сис­те­му команд про­цес­со­ра ARM под­роб­но мы не станем. По­ми­мо са­мой до­ку­мен­та­ции, доступ­ной на сай­те arm.com, хо­ро­шим по­со­би­ем яв­ля­ет­ся книга ARM System Developer’s Guide, Andrew Sloss et al. Morgan Kaufmann Publishers.

В ка­че­­ст­ве при­ме­ра воз­мож­но­стей ARM мы рас­смот­рим сравнитель­но но­вое рас­ши­рение, ко­то­рое ком­пания ARM рек­ла­ми­ру­ет под на­званием NEON.

Ко­ман­ды NEON поя­ви­лись в ар­хи­тек­ту­ре ARMv7 – прав­да, не во всех ее реа­ли­за­ци­ях. Про­цес­со­ры ARM Cortex A8 под­дер­жи­ва­ют NEON в обя­за­тель­ном по­ряд­ке, тогда как в про­цес­со­рах ARM Cortex A9 эта под­держ­ка необя­за­тель­на. Чип­сет Nvidia Tegra 2, ко­то­рый до сих пор ис­поль­зу­ет­ся в неко­то­рых план­ше­тах Android, не под­дер­жи­ва­ет NEON, а Nvidia Tegra 3 – под­дер­жи­ва­ет. NEON пред­став­ляет со­бой на­бор команд SIMD, то есть ин­ст­рук­ций, ко­то­рые способны ра­бо­тать с несколь­ки­ми единица­ми дан­ных од­но­вре­мен­но. В от­ли­чие от реа­ли­за­ций SIMD в неко­то­рых дру­гих ар­хи­тек­ту­рах, для ис­поль­зо­вания команд NEON про­цес­сор ARM не тре­бу­ет­ся пе­ре­во­дить в осо­бый ре­жим. Ин­ст­рук­ции NEON мож­но сво­бод­но сме­ши­вать с дру­ги­ми ин­ст­рук­ция­ми ARM.

В рас­по­ря­жении команд NEON 32 64-бит­ных ре­ги­ст­ра, ко­то­рые мож­но рас­смат­ри­вать так­же как 16 128-бит­ных ре­ги­ст­ров. С точ­ки зрения воз­мож­но­стей ин­ст­рук­ции NEON не слишком от­ли­ча­ют­ся от на­бо­ров ин­ст­рук­ций SIMD в про­цес­со­рах Intel. Это и неуди­витель­но – ведь соз­да­ва­лись они для ре­шения одних и тех же за­дач.

Рас­смот­рим про­стой, са­мый про­стой при­мер. Пусть у нас есть ка­нал сте­рео­зву­ка. Вве­дем немно­го тер­ми­но­ло­гии. Оциф­ро­ван­ный звук со­сто­ит из се­рии от­сче­тов [samples]. По­сколь­ку сте­рео­звук со­дер­жит два ка­на­ла, минималь­ная единица зву­ка – кадр [frame] со­сто­ит из двух от­сче­тов. В обыч­ном сте­рео­по­то­ке от­сче­ты пра­во­го и ле­во­го ка­на­лов че­ре­ду­ют­ся; таким образом, по­ток мож­но рас­смат­ри­вать как по­сле­до­ва­тель­ность кад­ров. При об­ра­ботке зву­ка (на­при­мер, фильт­ра­ции) бы­ва­ет необ­хо­ди­мо раз­де­лить от­сче­ты раз­ных ка­на­лов, об­ра­бо­тать их, а по­том восста­но­вить че­ре­до­вание.

(thumbnail)
Принцип работы команды vld2.

В сис­те­ме команд NEON су­ще­ст­ву­ют ко­ман­ды за­груз­ки че­ре­дую­щих­ся дан­ных с ав­то­ма­ти­че­­ским об­ра­щением че­ре­до­вания [deinterleaving] и со­хранения дан­ных с восста­нов­лением че­ре­до­вания. Мнемониче­­ские обо­зна­чения этих команд име­ют вид vld* и vst*, где * – чис­ло 1, 2, 3 или 4, со­от­вет­ст­вую­щее по­ряд­ку че­ре­до­вания дан­ных. Это же чис­ло обо­зна­ча­ет ко­ли­че­­ст­во ре­ги­ст­ров NEON, ко­то­рые долж­ны ис­поль­зо­вать­ся в про­цес­се об­ра­щения че­ре­до­вания. На­при­мер, при ра­бо­те со сте­рео­зву­ком, где мы име­ем два че­ре­дую­щих­ся ка­на­ла, со­от­вет­ст­вую­щие ко­ман­ды долж­ны вы­гля­деть как vld2 и vst2. Ес­ли бы нам нуж­но бы­ло об­ра­тить че­ре­до­вание от­сче­тов ин­тен­сив­но­сти цве­та в по­то­ке фор­ма­та RGBA, мы бы ис­поль­зо­ва­ли ко­ман­ды vld4 и vst4, при этом че­ты­ре за­дей­ст­во­ван­ных ре­ги­ст­ра NEON со­дер­жа­ли бы дан­ные R, G, B и A со­от­вет­ст­вен­но.

На этом ма­гия команд NEON не ис­чер­пы­ва­ет­ся. Че­ре­дую­щие­ся от­сче­ты мо­гут иметь раз­ную раз­ряд­ность: 8, 16, 24 и 32 би­та, и ко­ман­ды NEON по­зво­ля­ют учесть этот факт. На­при­мер, ес­ли нам нуж­но об­ра­тить че­ре­до­вание сте­рео­по­то­ка ау­дио­дан­ных раз­ряд­но­стью 16 бит, ко­ман­да за­груз­ки дан­ных бу­дет вы­гля­деть так: vld2.16. По­сле имени ко­ман­ды сле­ду­ет спи­сок ре­ги­ст­ров NEON для за­груз­ки дан­ных. Чис­ло этих ре­ги­ст­ров долж­но со­от­вет­ст­во­вать вы­бран­ной ко­ман­де. На­при­мер, для сте­рео­зву­ка эта ко­ман­да мо­жет вы­гля­деть так:

vld2.16 {d0, d1}, [r0]

Здесь d0 и d1 – ре­ги­ст­ры NEON, в ко­то­рые бу­дут за­гру­же­ны дан­ные, а вме­сто r0 мож­но ис­поль­зо­вать лю­бой обыч­ный ре­гистр ARM, ко­то­рый со­дер­жит ад­рес бло­ка дан­ных для за­груз­ки.

Итак, в ре­зуль­та­те вы­полнения опе­ра­ции vld2 в ре­ги­ст­ры d0 и d1 за­гру­жа­ют­ся от­сче­ты счи­тан­ных кад­ров, со­от­вет­ст­вую­щие пра­во­му и ле­во­му ка­на­лам. Сколь­ко же все­го кад­ров бы­ло счи­та­но? От­вет на этот во­прос по­зво­ля­ет оп­ре­де­лить, на­сколь­ко эф­фек­тив­но ис­поль­зо­вание NEON. Ре­ги­ст­ры d* яв­ля­ют­ся 64-бит­ны­ми. Это зна­чит, что в ка­ж­дый ре­гистр за­пи­са­но по 4 16-бит­ных от­сче­та, то есть все­го – 4 кад­ра. В прин­ци­пе, в ин­ст­рук­ции vld* допускается ис­поль­зо­вание 128-бит­ных ре­ги­ст­ровы q*; в этом случае ко­ли­че­­ст­во дан­ных, за­гру­жен­ных од­ной ин­ст­рук­ци­ей, уд­ва­ивает­ся.

На­пи­шем функ­цию swap_channels, ко­то­рая ме­ня­ет мес­та­ми ка­на­лы сте­рео.

swap_channels:

vld2.16 {d0, d1}, [r0]

vst2.16 {d1, d0}, [r0]

mov pc, lr

Эта функ­ция, как вы до­га­да­лись, на­пи­са­на на чис­том ас­семб­ле­ре, и ее необ­хо­ди­мо по­мес­тить в файл с рас­ши­рением s. Со­глас­но стан­дар­ту EABI, ре­гистр r0 ис­поль­зу­ет­ся для пе­ре­да­чи пер­во­го ар­гу­мен­та при вы­зо­ве функ­ции, так что из ко­да, на­пи­сан­но­го на C, ее следует вы­зы­вать так:

swap_channels(data);

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

Для сбор­ки про­ек­та с под­держ­кой NEON вам так­же по­на­до­бит­ся внести из­менения в фай­лы Application.mk и Android.mk. В пер­вом фай­ле пе­ре­мен­ной APP_ABI необходимо при­сво­ить зна­чение armeabi-v7a. Мож­но при­сво­ить и сра­зу два зна­чения: armeabi и armeabi-v7a. В этом слу­чае ваш код бу­дет ском­пи­ли­ро­ван в двух ва­ри­ан­тах – с под­держ­кой NEON и без оной. Ра­зу­ме­ет­ся, это воз­мож­но толь­ко в том слу­чае, ес­ли ис­ход­ные тек­сты не со­дер­жат ас­семб­лер­ных вста­вок, ис­поль­зую­щих ин­ст­рук­ции NEON. Да­лее сле­ду­ет вве­сти пе­ре­мен­ную

LOCAL_ARM_MODE := NEON

И это еще не все. До­пустим, что функ­ция swap_channels со­хранена в фай­ле swapchannels.s. В таком слу­чае в фай­ле Android.mk в пе­ре­мен­ной LOCAL_SRC_FILES имя это­го фай­ла долж­но быть ука­за­но как swapchannels.s.neon (толь­ко в Android.mk, но не на дис­ке!). Так же сле­ду­ет по­сту­пать и с дру­ги­ми мо­ду­ля­ми, в ко­то­рых тре­бу­ет­ся под­держ­ка NEON. Хо­тя необ­хо­ди­мость до­бав­лять окон­чания neon мо­жет по­ка­зать­ся стран­ной, на са­мом де­ле это весь­ма удоб­ный спо­соб от­де­лить мо­ду­ли, в ко­то­рых тре­бу­ет­ся под­держ­ка NEON, от мо­ду­лей, в ко­то­рых она не нуж­на.

Root не ну­жен

В про­шлых ста­тях я пи­сал о том, что для за­пуска про­грамм Linux под управ­лением Android по­тре­бу­ет­ся ру­то­ван­ное уст­рой­ст­во или про­грамм­ный эму­ля­тор. Но жизнь не сто­ит на мес­те, и я рад со­об­щить вам, что есть за­ме­ча­тель­ная про­грам­ма, ко­то­рая по­зво­лит вам ра­бо­тать с уст­рой­ст­вом Android поч­ти так же, как с лю­бой ма­ши­ной под управ­лением Linux, в том чис­ле за­пускать про­грам­мы для Linux, и все это – не тре­буя прав су­пер­поль­зо­ва­те­ля! Кро­ме то­го, данная про­грам­ма от­но­сит­ся к ка­те­го­рии про­грамм, спо­соб­ных раз­ра­ба­ты­вать при­ло­жения для Android на са­мом уст­рой­ст­ве Android (и по­яв­ление та­ких про­грамм я то­же пред­ска­зы­вал!).

Речь идет о про­грам­ме под на­званием Terminal IDE. На пер­вый взгляд это на­звание вы­гля­дит как «Уж и Еж», но не все так про­сто. Па­кет Terminal IDE со­дер­жит весь необ­хо­ди­мый ин­ст­ру­мен­та­рий для сбор­ки, от­лад­ки и за­пуска про­грамм Java. При­чем конеч­ные фай­лы со­би­ра­ют­ся не в фор­ма­те APK, ко­то­рый тре­бу­ет уста­нов­ки, а в обыч­ном для Java фор­ма­те (прав­да, байт-код рас­счи­тан на фир­мен­ную вир­ту­аль­ную ма­ши­ну Android). Впро­чем, как мы с ва­ми до­го­во­ри­лись, раз­ра­бот­ка про­грамм Java в рам­ках этой се­рии нас не ин­те­ре­су­ет, а ин­те­ре­су­ет нас то, что все ин­ст­ру­мен­ты для ра­бо­ты с Java в Terminal IDE яв­ля­ют­ся стан­дарт­ны­ми кон­соль­ны­ми про­грам­ма­ми (javac, java и т. д.), а, ста­ло быть, па­ке­ту не обой­тись без эму­ля­то­ра тер­ми­на­ла, ко­то­рый он и реа­ли­зу­ет (и де­ла­ет это луч­ше, чем дру­гие ви­ден­ные мной про­грам­мы). Кста­ти, ес­ли уж речь за­шла об име­нах: про­грам­ма, на­пи­сан­ная раз­ра­бот­чи­ком, на­звав­шим­ся Spartacus Rex (име­ет­ся в ви­ду вождь восстав­ших ра­бов, а не фут­боль­ная ко­ман­да), за­слу­жи­вает внимания в си­лу од­но­го это­го фак­та.

По­ми­мо эму­ля­то­ра тер­ми­на­ла, мы по­лу­ча­ем в свое рас­по­ря­жение обо­лоч­ку bash, на­бор ути­лит busybox, на­бор про­грамм для ра­бо­ты с GIT и про­грам­му Midnight Commander. Кро­ме то­го, па­кет об­ла­да­ет за­ме­ча­тель­ной про­грам­ми­ст­ской кла­виа­ту­рой (она за­слу­жи­ва­ет от­дель­но­го опи­сания), воз­мож­но­стью под­клю­чать­ся к дру­гим сис­те­мам с по­мо­щью про­грамм telnet и ssh, а так­же воз­мож­но­стью под­клю­чать­ся к уст­рой­ст­ву Android с по­мо­щью тех же telnet и ssh. В по­следнем слу­чае при­дет­ся ис­поль­зо­вать так на­зы­вае­мый об­рат­ный туннель SSH – ре­жим, в ко­то­ром кли­ент SSH, за­пу­щен­ный на ком­пь­ю­те­ре, мо­жет пре­достав­лять доступ дру­гим сис­те­мам через SSH. По-мо­ему, все вы­ше­пе­ре­чис­лен­ное уже зву­чит доста­точ­но за­ман­чи­во для то­го, что­бы по­спе­шить с уста­нов­кой Terminal IDE в свою сис­те­му. Но глав­ное со­кро­ви­ще Terminal IDE для нас – это соб­ст­вен­ная фай­ло­вая сис­те­ма па­ке­та, ко­то­рая да­ет нам пол­ный кон­троль над фай­ла­ми, в том чис­ле по­зво­ля­ет при­сваи­вать им ста­тус ис­пол­няе­мых. При этом раз­де­ляе­мые биб­лио­те­ки Android оста­ют­ся ви­ди­мы­ми для про­грамм, ра­бо­таю­щих в этой фай­ло­вой сис­те­ме.

Ме­ж­ду про­чим, в ру­ко­во­дстве поль­зо­ва­те­ля Terminal IDE, ко­то­рое ав­тор про­грам­мы со­ве­ту­ет про­честь не менее двух раз, пре­ж­де чем за­да­вать во­про­сы ему, на­пи­са­но, что вы­пол­нять ди­на­ми­че­­ски ском­по­но­ван­ные про­грам­мы Linux из тер­ми­на­ла нель­зя, так как ди­на­ми­че­­ский за­груз­чик Android от­ли­ча­ет­ся от стан­дарт­но­го за­груз­чи­ка Linux. Мы уже зна­ем, как обой­ти дан­ное ог­раничение, так что это пре­досте­ре­жение не для нас (в ка­че­­ст­ве де­мон­ст­ра­ции за­пустим в Terminal IDE ском­по­но­ван­ную ди­на­ми­че­­ски про­грам­му threads, ис­ходники ко­то­рой вы най­де­те на дис­ке).

Прой­дя про­це­ду­ру уста­нов­ки про­грам­мы и ее на­строй­ки че­рез стар­то­вый эк­ран (кста­ти, са­мые ин­те­рес­ные оп­ции на­стро­ек доступ­ны в раз­де­ле на­стро­ек на стар­то­вом эк­ране, а не в обыч­ном раз­де­ле на­стро­ек при­ло­жения Android), вы смо­же­те за­пускать тер­ми­нал (про­грам­ма по­зво­ля­ет от­крыть несколь­ко окон тер­ми­нала, ме­ж­ду ко­то­ры­ми пе­ре­клю­чают­ся, «про­лис­ты­вая» их на сен­сор­ном эк­ране). Сре­ди на­бо­ра доступ­ных нам про­грамм, к со­жа­лению, нет ком­пи­ля­то­ра GCC, ведь, как уже бы­ло ска­за­но Terminal IDE пред­на­зна­че­на для раз­ра­бот­ки про­грамм на Java. Од­на­ко ничто не ме­ша­ет нам ис­поль­зо­вать тер­ми­нал для за­пуска про­грамм, со­б­ран­ных на ПК с по­мо­щью средств ARM GCC.

Ко­пи­ру­ем ис­пол­няе­мый файл та­кой про­грам­мы в од­ну из ди­рек­то­рий на­ше­го уст­рой­ст­ва, и с по­мо­щью Terminal IDE пы­та­ем­ся при­сво­ить фай­лу ат­ри­бут ис­пол­няе­мо­го. Ни­че­го не про­ис­хо­дит. При­чи­на в том, что мы ра­бо­та­ем не там, где на­до. Что­бы по­лу­чить власть над фай­лом про­грам­мы, нам на­до ско­пи­ро­вать или пе­ре­мес­тить его в соб­ст­вен­ную фай­ло­вую сис­те­му Terminal IDE. В окне тер­ми­на­ла вводим следующую последовательность ко­ман­д:

cp program ~

cd ~

chmod 777 program

./program

Здесь program – ис­пол­няе­мый файл про­грам­мы Linux.

Для рас­по­ло­жения соб­ст­вен­ной фай­ло­вой сис­те­мы Terminal IDE ис­поль­зует ди­рек­то­рию /data/data/com.spartacusrex.spartacuside/files, то есть од­ну из тех ди­рек­то­рий, к ко­то­рым на неру­то­ван­ном уст­рой­ст­ве по­лу­чить доступ с по­мо­щью обыч­но­го тер­ми­на­ла нель­зя. Terminal IDE по­лу­ча­ет доступ к этой ди­рек­то­рии как «че­ст­ное» при­ло­жение Android. Та­ким об­ра­зом, мы сно­ва ис­поль­зу­ем воз­мож­но­сти при­ло­жений Android для то­го, что­бы рас­ши­рить воз­мож­но­сти при­ло­жений Linux.

С точ­ки зрения са­мой про­грам­мы Terminal IDE ука­зан­ная ди­рек­то­рия пред­став­ля­ет­ся как до­маш­няя ди­рек­то­рия поль­зо­ва­те­ля (имен­но на нее ука­зы­ва­ет тиль­да при ра­бо­те в bash). Внут­ри этой ди­рек­то­рии вы неожи­дан­но най­де­те под­ди­рек­то­рии bin и lib. Ди­рек­то­рия bin со­дер­жит все про­грам­мы, ко­то­ры­ми поль­зу­ет­ся Terminal IDE (про­грам­мы ском­по­но­ва­ны ста­ти­че­­ски), а ди­рек­то­рия lib ниче­го не со­дер­жит.

В за­клю­чение от­ме­чу, что про­грам­ма весь­ма тре­бо­ва­тель­на к объ­е­мам па­мя­ти. В хранили­ще при­ло­жение занима­ет 104 ме­га­бай­та (в мо­ей сис­те­ме это ре­корд). В опе­ра­тив­ной па­мя­ти за­ня­тый объ­ем ко­леб­лет­ся в рай­оне 20 ме­га­байт. В этот объ­ем, ра­зу­ме­ет­ся, не вхо­дят про­грам­мы, за­пу­щен­ные из тер­ми­на­ла, ко­то­рые во­об­ще неви­ди­мы для сис­те­мы. Так что при ра­бо­те с Terminal IDE на уст­рой­ст­вах с неболь­шим объ­е­мом опе­ра­тив­ной па­мя­ти сле­ду­ет со­блю­дать осто­рож­ность. Terminal IDE отнюдь не слу­чай­но является од­ной из тех про­грамм, ко­то­рые ис­поль­зу­ют стро­ку со­стояния Android для пре­ду­пре­ж­дения пользователя о том, что они за­пу­ще­ны. Кро­ме то­го, про­грам­ма до­пуска­ет яв­ное за­вер­шение сво­ей ра­бо­ты, а не пе­ре­кла­ды­ва­ет этот про­цесс на пле­чи Android.

Ес­ли ха­ке­ров мож­но в ка­ком-то смыс­ле сравнить с ры­ца­ря­ми, то курс мо­ло­до­го ры­ца­ря на этом за­кон­чен. Вы вла­дее­те всем необ­хо­ди­мым, что­бы про­дол­жить са­мо­стоя­тель­ное изу­чение тон­ко­стей ме­ханики ARM и Android для рас­ши­ре­ния воз­мож­но­стей этой ОС. |

Персональные инструменты
купить
подписаться
Яндекс.Метрика