Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Написал порт scmRTOS под ARM
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
Страницы: 1, 2
Сергей Борщ
Цитата(sergeeff @ May 20 2006, 17:01) *
Я давно приглядываюсь к scmRTOS применительно к ARM. На работе текучка одолела – совершенно невозможно заняться чем-то впрок.

Но есть соображение насчет скорости работы scheduler’а. Поиск текущей приоритетной задачи осуществляется простым перебором бит в слове состояния процессов. Очевидно, что время поиска прямо пропрционально номеру приоритета, что не есть хорошо. Есть классная функция, оптимизированная для ARM, которая выдает номер младшего установленного бита в 32-разрядном слове (она используется в Linux’e и TNKernel). Вот ее текст:
Думаю полезно эту функцию использовать вместо стандартного решения.
Отлично! Гарри как раз спрашивал, если такая аппаратная функция в ARM. Обязательно добавлю.


Цитата(ig_z @ May 20 2006, 18:01) *
Посмотрел ваш порт, сходу возник архитектурный вопрос.
Почему для контекст свитчера вы выбрали ВИК-СВ а не просто СВИ, были какие то особые причины? СВИ - ядерная часть и поэтому есть у всех, а ВИК - периферия, имеет право и не быть в кристалле.
Дело в том, что SWI всем хороша кроме одного - она исполняется независимо от того - разрешены прерывания или нет. Когда переключение вызывается из потока проблем нет, а вот когда из прерывания возникают сложности - обработчик прерывания уже наложил в стек своей информации. Идеология scmRTOS предполагает, что при возникновении необходимости переключения контекста внутри обработчика прерывания взводится запрос на переключение а собственно прерывание переключателя вызывается в момент выхода из обработчика, когда разрешаются глобальные прерывания и стек содержит только данные текущего потока. Я подумывал о использовании SWI при вызове из потока и "честного" прерывания при переключении в обработчике прерывания, но это потребовало бы изменения "непортируемой" части ОС, хотя и ускорило бы переключение. В общем я пока в раздумье. Есть идеи?


Цитата(sergeeff @ May 20 2006, 18:32) *
Да. Еще про critical section. Твой вариант запрета/разрешения прерываний не очень безопасный вариант (см. Atmel Application Note Rev. 1156A–08/98) и лучше это сделать так, как сделано в UCOS порте для ARM:
...........
Работает дольше, но безопаснее
Спасибо, не знал (только начинаю ARM осваивать). Изучу и учту. Собственно ради таких замечаний и открыл это обсуждение.
ig_z
Цитата(Сергей Борщ @ May 20 2006, 20:50) *
Цитата(ig_z @ May 20 2006, 18:01) *

Посмотрел ваш порт, сходу возник архитектурный вопрос.
Почему для контекст свитчера вы выбрали ВИК-СВ а не просто СВИ, были какие то особые причины? СВИ - ядерная часть и поэтому есть у всех, а ВИК - периферия, имеет право и не быть в кристалле.
Дело в том, что SWI всем хороша кроме одного - она исполняется независимо от того - разрешены прерывания или нет. Когда переключение вызывается из потока проблем нет, а вот когда из прерывания возникают сложности - обработчик прерывания уже наложил в стек своей информации. Идеология scmRTOS предполагает, что при возникновении необходимости переключения контекста внутри обработчика прерывания взводится запрос на переключение а собственно прерывание переключателя вызывается в момент выхода из обработчика, когда разрешаются глобальные прерывания и стек содержит только данные текущего потока. Я подумывал о использовании SWI при вызове из потока и "честного" прерывания при переключении в обработчике прерывания, но это потребовало бы изменения "непортируемой" части ОС, хотя и ускорило бы переключение. В общем я пока в раздумье. Есть идеи?


Если я правильно понимаю - SWI имеет 6-й наинизший приоритет, поэтому не может прерывать FIQ (3) IRQ (4). Иначе вообще не понятно, зачем нужны софт прерывания.


Цитата(Сергей Борщ @ May 20 2006, 20:50) *
Цитата(sergeeff @ May 20 2006, 18:32) *

Да. Еще про critical section. Твой вариант запрета/разрешения прерываний не очень безопасный вариант (см. Atmel Application Note Rev. 1156A–08/98) и лучше это сделать так, как сделано в UCOS порте для ARM:
...........
Работает дольше, но безопаснее
Спасибо, не знал (только начинаю ARM осваивать). Изучу и учту. Собственно ради таких замечаний и открыл это обсуждение.


Возможно достаточно использовать интринсики __disable_interrupt __enable_interrupt. Соответствуют рекомендациям АРМ. Но они вроде бы не инлайнятся.
Dainis
Цитата(ig_z @ May 20 2006, 18:01) *
Почему для контекст свитчера вы выбрали ВИК-СВ а не просто СВИ, были какие то особые причины? СВИ - ядерная часть и поэтому есть у всех, а ВИК - периферия, имеет право и не быть в кристалле.
Если я правильно понимаю - SWI имеет 6-й наинизший приоритет, поэтому не может прерывать FIQ (3) IRQ (4). Иначе вообще не понятно, зачем нужны софт прерывания.


SWI единственный коректный способ вызова SYSTEM (protected) режима или функций от USER режима.
GetSmart
Цитата
SWI единственный коректный способ вызова SYSTEM (protected) режима или функций от USER режима.

Можно узнать почему?
_____________________
Кстати, если откуда-то вызывается критическая секция, то смысла её вызывать из USER нет никакого. Всё-равно прерывания не запретятся. Именно на этот счёт так усложнилась процедура их запрещения. Хотя пользы от неё почти никакой. Сразу задача зависнет.
Цитата
Возможно достаточно использовать интринсики __disable_interrupt __enable_interrupt. Соответствуют рекомендациям АРМ. Но они вроде бы не инлайнятся.

Не инлайнятся потому, что работают только а ARM-режиме и чтобы их выполнить из THUMB нужно вызвать ARM-процедуру (__disable_interrupt) и вернуться обратно. __disable_interrupt тоже завесит задачу если её вызвать из USER.

Хотя если... если очень постараться, то можно. Можно запрещать прерывания USER-режима переключаясь в SYSTEM, а потом обратно. Например через тот же SWI. Но это будет ещё медленней (немного).
zltigo
Цитата
Можно хотябы в общих словах пример кода, я его погоняю.

Думаю сам все сделаю через недельку и доложу. Пока начал немножко ядрышко подчищать :-),
в том числе в части critical section для ядра и для приложений.
Цитата(GetSmart @ May 21 2006, 15:36) *
Не инлайнятся потому, что работают только а ARM-режиме и....

C Thumb дело ясное, но IAR какое-то темноватое дело получается :-( __disable_interrupt()
не инлайнится и в ARM mode, хотя должна.
Цитата
Возможно достаточно использовать интринсики __disable_interrupt

Достаточно, в __disable_interrupt() именно такой код 'с контролем' и заложен, вопрос только в том,
нужны-ли эти выкрутасы не для Atmel-овских чипов. Кроме того, не совсем ясно, нужны-ли эти
выкрутасы не при одновременном запрещении IRQ и FIQ.
Еще к размышлению - а почему в порте только IRQ запрещаются? Если использовать исключительно для использования в ядре, то можно подумать и о варианте (скорость?) блокировки только таймерного прерывания. Но тупое-полное для приложений тоже реализовано должно быть.
GetSmart
Цитата
вопрос только в том,
нужны-ли эти выкрутасы не для Atmel-овских чипов. Кроме того, не совсем ясно, нужны-ли эти
выкрутасы не при одновременном запрещении IRQ и FIQ.

А причём тут Атмел? Это "выкрутасы" для стандартного АРМ-ядра нужны. Вышепроцитированная проверка делается только для USER-режима, в котором нельзя запрещать прерывания. Это не связано с "глюками" процов. Там немного другие проверки. Есть такой глюк, когда при одновременном запрещении IRQ и FIQ может сработать в последний момент прерывание, но в этом порте как я понял FIQ вообще не используется и либо нужно придумать ещё одну особую критическую секцию, либо оставить всё на совести пользователя. А на переключение контекстов они вроде как не влияют.
zltigo
Цитата(GetSmart @ May 21 2006, 20:38) *
А причём тут Атмел?

У других жалоб не встречал.
Цитата
Это "выкрутасы" для стандартного АРМ-ядра нужны.

Я дважды натыкался на комментарии типа такого:
Код
        mrs    r0,CPSR
        orr    r1,r0,#NOINT
        msr    CPSR_c,r1
;-- Atmel add-on
        mrs    r1,CPSR             ;-- Check CPSR for correct contents
        and    r1,r1,#NOINT
        cmp    r1,#NOINT
        bne    tn_cpu_save_sr      ;-- Not disabled - loop to try again
;--------
        mov    pc, lr

Ссылка на источник от _ARM_ есть?

Цитата
Вышепроцитированная проверка делается только для USER-режима, в котором нельзя запрещать прерывания.

О чем Вы это? Когда нельзя - тогда НЕЛЬЗЯ! А тут элементарный контроль на то, что флаги установились.
Цитата
но в этом порте как я понял FIQ вообще не используется и либо нужно придумать ещё одну особую критическую секцию, либо оставить всё на совести пользователя. А на переключение контекстов они вроде как не влияют.

Ага, не влияют - я, например, из обработчика FIQ какой-нибудь семафорчик (Signal) дерну, который шедулер за собой потянет...
GetSmart
Самая близкая по смыслу цитата из <arm7tdmi.pdf>:
Цитата
3.9.5 IRQ
The IRQ (Interrupt Request) exception is a normal interrupt caused by a LOW level on
the nIRQ input. IRQ has a lower priority than FIQ and is masked out when a FIQ
sequence is entered. It may be disabled at any time by setting the I bit in the CPSR,
though this can only be done from a privileged (non-User) mode.

- самый конец цитаты.
Хотя весь файл напичкан ограничениями в USER-режиме. Вообще, все эти заморочки стоит учитывать если Ось (порт) будет выполняться в этом режиме. Если же нет, то и заморачиваться не стоит.

Цитата
О чем Вы это? Когда нельзя - тогда НЕЛЬЗЯ!


Ну дак это же не убьёт процессор. Просто команда выполнится в холостую. Для этого и проверка, чтобы у USER-проги и желания такого не возникало. Чисто через SYSTEM. А иначе - "висеть вам вечно".

Цитата
Ага, не влияют - я, например, из обработчика FIQ какой-нибудь семафорчик (Signal) дерну, который шедулер за собой потянет...

Я ведь написал - на совести пользователя. Хотя в FIQ делать что-то сложное и вызывать систему вообще не рекомендуется. Так, по-быстренькому обратиться к портам или переменным и назад.
Сергей Борщ
Завтра (в понедельник) буду изучать все это вдумчиво. Пока быстрый ответ такой:
1) приложение исполняется в System mode чтобы можно было запрещать/разрешать прерывания не задействуя SWI с разбором кода команды и ветвлением на функции запрета/разрешения/прочего.
2) FIQ не запрещется ибо предполагается что на то оно и быстрое чтобы из него сервисы ОС не вызывались.
3) __disable_interrupt()/__enable_interrupt() не используются именно потому что запрещает и IRQ и FIQ (см. п. 2).
4) Не разобрался до конца со SWI но мне кажется что если его вызвать внутри обработчика IRQ то проц уйдет в исключение SWI если не сразу то в момент переключения в System mode при разрешении вложенных прерываний. Буду изучать детальнее.
5) Идея при входе в критическую секцию запрещать не все прерывания а только прерывание переключения контекста была мною предложена автору scmRTOS недели 2-3 назад, он ее пока думает.
GetSmart
1,2,3 - OK
4. Как только в коде (даже в IRQ, и даже при запрещённых прерываниях) встречается SWI, то сразу вызывается обработчик SWI. В этом смысле у неё нет никаких приоритетов.
zltigo
Цитата(GetSmart @ May 21 2006, 22:08) *
Просто команда выполнится в холостую. Для этого и проверка, чтобы у USER-проги и желания такого не возникало.

А посмотреть на исходник и почитать Аtmel-овский AN (кстати посвященный отнюдь не ядру
а периферии )
Цитата
Хотя в FIQ делать что-то сложное и вызывать систему вообще не рекомендуется. Так, по-быстренькому обратиться к портам или переменным и назад.

C чего-бы это вдруг (это если безотносительно к ограничениям конкретной реализации чего-либо).
Совершенно нормальный источник. Даже "Быстрый". По крайней мере приоритетный и со своим
стеком, что позволяет без дополнительных наворотов вложенность в обычные IRQ реализовывать.
zltigo
Цитата(Сергей Борщ @ May 21 2006, 22:42) *
2) FIQ не запрещется ибо предполагается что на то оно и быстрое чтобы из него сервисы ОС не вызывались.
4) Не разобрался до конца со SWI но мне кажется что если его вызвать внутри обработчика IRQ то проц уйдет в исключение SWI если не сразу то в момент переключения в System mode при разрешении вложенных прерываний. Буду изучать детальнее.
5) Идея при входе в критическую секцию запрещать не все прерывания а только прерывание переключения контекста была мною предложена автору scmRTOS недели 2-3 назад, он ее пока думает.

2) Я тоже склонился к такой мысли в процессе "подгонки под себя" FreeRTOS.
Однако при этом остался открытый вопрос с ритуальными плясками от Atmel :-( пока оставлены
из исходя принципа "береженого бог бережет".
4) Сразу. А какие проблемы предполагаются?
5) Это я, как вариант, у себя сделал, тем более во FreeRTOS были уже заложены два отдельных,
но одинаково определенных вызова. Но использовать скорее всего не буду - у меня чаще всего
источник системного прерывания переключается на внешний в процессе работы.
GetSmart
Цитата
А посмотреть на исходник и почитать Аtmel-овский AN (кстати посвященный отнюдь не ядру, а периферии )

У-у-у. А с каких пор CPSR стал периферией?
Хотя если дадите ссылку, то прочту.

Цитата
Совершенно нормальный источник. Даже "Быстрый". По крайней мере приоритетный и со своим
стеком, что позволяет без дополнительных наворотов вложенность в обычные IRQ реализовывать.

Для таких как вы, придётся делать ещё одну критическую секцию "spatial for FIQ".
zltigo
Цитата(GetSmart @ May 21 2006, 23:34) *
У-у-у. А с каких пор CPSR стал периферией?
Хотя если дадите ссылку, то прочту.

Не стал, но завязан :-(
А AIC уже в официальных Atmel-овских бумагах выступает как периферия ядра.

Ссылка на единственный источник:
http://www.atmel.com/dyn/resources/prod_do...nts/DOC1156.PDF
GetSmart
Цитата
Не стал, но завязан :-(
А AIC уже в официальных Atmel-овских бумагах выступает как периферия ядра.

Ну и ничего нового не узнал. :-( Смысл ПДФ-а в том, что внутри обработчиков исключений нужно осторожно анализировать/менять регистр SPSR, или по-другому - писать нормально обработчики прерываний. Там вообще ситуация, когда внутри обработчика в регистре SPSR какой-то дурень принудительно разрешает прерывания. Это делать просто не надо. Ни у меня, ни у Сергея Борща такого изврата нет. Глюк-то не периферийный, а программный. Точнее криво-программный.
Кстати, это не только для Атмела, но и в LPC такая же бяка.
zltigo
Цитата(GetSmart @ May 22 2006, 00:28) *
Ну и ничего нового не узнал.

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

Выводы - на прибамбасы в __disable_interrupts() наплевать и забыть.....

Цитата
Кстати, это не только для Атмела, но и в LPC такая же бяка.

Похоже :-(. Косвенное подтверждение этому можно получить из описания процесса организации вложенных прерываний на LPC.
amusin
Цитата(sergeeff @ May 20 2006, 20:01) *
Есть классная функция, оптимизированная для ARM, которая выдает номер младшего установленного бита в 32-разрядном слове (она используется в Linux’e и TNKernel). Вот ее текст:
...
Очевидно имеем констатное время поиска номера младшего установленного бита в слове.
Думаю полезно эту функцию использовать вместо стандартного решения.


Мысль (именно в приложении к шедулингу) очень здравая. Только приведенный код можно реализовать и на Си (например, первые 2 команды - выделение младшей единички на Си x&(-x))
C точки зрения алгоритмов здесь суть найти число нулей справа
(при #define scmRTOS_PRIORITY_ORDER 0 конечно).

Реализаций этой функции - море: http://www.hackersdelight.org/HDcode/ntz.cc

Для другого порядка приоритетов в карте
#define scmRTOS_PRIORITY_ORDER 1 надо использовать функции из
http://www.hackersdelight.org/HDcode/nlz.cc
amusin
Цитата(Сергей Борщ @ May 20 2006, 23:50) *
Отлично! Гарри как раз спрашивал, если такая аппаратная функция в ARM. Обязательно добавлю.

Нет в АРМ такой инструкции. Взгляни, например, на команду PRIOR в C166
http://www.keil.com/dd/docs/datashts/infineon/c166ism.pdf
Сергей Борщ
Цитата(amusin @ May 22 2006, 08:32) *
Цитата(Сергей Борщ @ May 20 2006, 23:50) *

Отлично! Гарри как раз спрашивал, если такая аппаратная функция в ARM. Обязательно добавлю.

Нет в АРМ такой инструкции.

Я так и ответил :-)
aaarrr
Цитата(amusin @ May 22 2006, 09:32) *
Нет в АРМ такой инструкции. Взгляни, например, на команду PRIOR в C166
http://www.keil.com/dd/docs/datashts/infineon/c166ism.pdf

Есть у ARM такая инструкция, правда, начиная с архитектуры V5 - CLZ называется.
Сергей Борщ
Спасибо всем, кто подсказал функцию поиска наиболее приоритетного процесса. Просмотрел все варианты на http://www.hackersdelight.org/HDcode/ntz.cc, наиболее эффективно компилится вариант подсказанный sergeeff за счет замены + на |. Теперь пытаюсь разобраться как оно работает чтобы написать такую же но для x = 8 и 16 бит. Кто-нибудь такое делал?
amusin
Цитата(Сергей Борщ @ May 22 2006, 17:30) *
Теперь пытаюсь разобраться как оно работает чтобы написать такую же но для x = 8 и 16 бит. Кто-нибудь такое делал?

А нафиг? GetHighPriority() лежит в порте ОС, т.е. предназначена именно для ARM. Сделать внутри этой функции static_cast аргумента для ntz (на взгляд профана в С++) - из большей корзины не выпадет, а для nlz из результата вычесть 8 или 24 внутри препроцессорных #if #else.
Сергей Борщ
Цитата(amusin @ May 22 2006, 14:48) *
Цитата(Сергей Борщ @ May 22 2006, 17:30) *

Теперь пытаюсь разобраться как оно работает чтобы написать такую же но для x = 8 и 16 бит. Кто-нибудь такое делал?

А нафиг? GetHighPriority() лежит в порте ОС, т.е. предназначена именно для ARM. Сделать внутри этой функции static_cast аргумента для ntz (на взгляд профана в С++) - из большей корзины не выпадет, а для nlz из результата вычесть 8 или 24 внутри препроцессорных #if #else.
Просто в зависимости от количества процессов аргумент для ntz 8, 16 или 32 бит. Или не морочить голову - вроде много тут уже не наэкономить?
IgorKossak
Цитата(Сергей Борщ @ May 22 2006, 14:59) *
Просто в зависимости от количества процессов аргумент для ntz 8, 16 или 32 бит. Или не морочить голову - вроде много тут уже не наэкономить?

Вот именно!
Вознёй с 8 и 16-битными вариантами можно только замедлить работу.
sergeeff
Да, конечно, для ARM надо сделать количество процессов - 32. Будет проще и быстрее.
amusin
Цитата(sergeeff @ May 22 2006, 21:40) *
Да, конечно, для ARM надо сделать количество процессов - 32. Будет проще и быстрее.

Кол-во процессов scmRTOS_PROCESS_COUNT надо делать таким, сколько требуется, и не больше, т.к. внутри void OS::TKernel::SystemTimer() есть цикл for(byte i = 0; i < scmRTOS_PROCESS_COUNT; i++).Другое дело, что может быть действительно сделать TProcessMap 32-разрядным, хотя это и не вписывается в идеологию scmRTOS об экономии ОЗУ smile.gif.
Как вариант, ширину TProcessMap можно сделать по потребности, а ее уже приводить к 32 разрядам внутри GetHighPriority(), если позволит компилятор.
Сергей Борщ
Цитата(amusin @ May 23 2006, 05:32) *
Цитата(sergeeff @ May 22 2006, 21:40) *

Да, конечно, для ARM надо сделать количество процессов - 32. Будет проще и быстрее.

Кол-во процессов scmRTOS_PROCESS_COUNT надо делать таким, сколько требуется, и не больше, т.к. внутри void OS::TKernel::SystemTimer() есть цикл for(byte i = 0; i < scmRTOS_PROCESS_COUNT; i++).Другое дело, что может быть действительно сделать TProcessMap 32-разрядным, хотя это и не вписывается в идеологию scmRTOS об экономии ОЗУ smile.gif.
Как вариант, ширину TProcessMap можно сделать по потребности, а ее уже приводить к 32 разрядам внутри GetHighPriority(), если позволит компилятор.
Да, так и будем делать - размер TProcessMap минимально необходимый. Про 32 процесса с Гарри утрясли, про GetHighPriority() - для кол-ва процессов меньше 7 сделаю табличный метод - размер таблицы будет те же 64 байта для 6 процессоа, для меньшего кол-ва соответственно меньше.

Беру тайм-аут до выходных - в пятницу надо изделие на выставку отправлять а софт еще процентов на 40 написан.
sergeeff
Пара соображений по поводу быстродействия. Я эту идею уже как-то Harry предлагал, но он посчитал это изменение не слишком принципиальным. Тем не менее, считаю целесообразным переписать пару функций, которые многократно вызываются в ядре и scheduler’e:

void SetPrioTag(TProcessMap& pm, const byte pr) { pm |= 1 << pr; }
void ClrPrioTag(TProcessMap& pm, const byte pr) { pm &= ~(1 << pr); }

Эти две функции выполняются быстрее (короче аналога на одну команду) и отпадает необходимость в массиве TProcessMap PrioMaskTable[scmRTOS_PROCESS_COUNT+1] и функции TProcessMap GetPrioTag(const byte pr) const { return PrioMaskTable[pr]; }.

Выигрыш не велик – но все же – короче и быстрее.
IgorKossak
Цитата(sergeeff @ May 26 2006, 20:34) *
...Выигрыш не велик – но все же – короче и быстрее.

Если отпадает необходимость в массиве, то это, как по мне, уже достаточно весомый аргумент ЗА.
dxp
Цитата(sergeeff @ May 27 2006, 00:34) *
void SetPrioTag(TProcessMap& pm, const byte pr) { pm |= 1 << pr; }
void ClrPrioTag(TProcessMap& pm, const byte pr) { pm &= ~(1 << pr); }

Эти две функции выполняются быстрее (короче аналога на одну команду) и отпадает необходимость в массиве TProcessMap PrioMaskTable[scmRTOS_PROCESS_COUNT+1] и функции TProcessMap GetPrioTag(const byte pr) const { return PrioMaskTable[pr]; }.

Выигрыш не велик – но все же – короче и быстрее.

За счет чего выигрыш? У АРМа есть аппаратный сдвигатель?
amusin
Цитата(dxp @ May 29 2006, 09:46) *


Гарри, ты не добавил const в объявлении
TProcessMap PrioMaskTable[scmRTOS_PROCESS_COUNT+1]
чтобы не заморачиваться с инициализацией таблицы?
GetSmart
Цитата
У АРМа есть аппаратный сдвигатель?

Конечно есть. Сдвигает регистр в 1-тактовой команде на любое количество бит.
spf
Цитата(GetSmart @ May 29 2006, 15:20) *
Цитата
У АРМа есть аппаратный сдвигатель?

Конечно есть. Сдвигает регистр в 1-тактовой команде на любое количество бит.

Необходимо уточнить ГДЕ задается "любое количество бит".
Если в другом регистре то хорошо.

PS:
Имеются процы у которых количество бит сдвига задается в коде команды - любое но фиксированное.
GetSmart
АРМ имеет команды с обоими вариантами: как непосредственное число сдвига, так и из содержимого другого регистра.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.