dxp 53 30 марта, 2010 Опубликовано 30 марта, 2010 · Жалоба Ну а почему нет? :) Зачем заранее рубить возможные расширения? Это ж не винда:) Да и винду расковыряли, несмотря ни на что. Я ж грю, не предполагалось, т.к. и в голову не приходило, что кому-то это может понадобиться в посторонних целях. Видимо, пожелание надо учесть, тем более, что это не тянет за собой проблем с производительностью и совместимостью. Дык, элементарно: Э-э... А запускать ты их откуда собрался? А как тут все это вяжется со стековым кадром? И самое главное - зачем все это? Какие преимущества это дает? Зачему тут динамический полиморфизм, когда все механизмы исходно статические? Имхо, весьма элегантно. Может даже и выигрыш есть... Завтра попробую в работе:) Давай. Расскажешь потом. :) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 30 марта, 2010 Опубликовано 30 марта, 2010 · Жалоба Видимо, пожелание надо учесть, тем более, что это не тянет за собой проблем с производительностью и совместимостью. Ура :) Э-э... А запускать ты их откуда собрался? А как тут все это вяжется со стековым кадром? Вроде я всё написал. В конструкторе TBaseProcess вместо адреса статического Exec() используется адрес глобальной функции start_process(). А она уже определяет текущий процесс и запускает его функцию Exec(). Вся эта возня происходит всего один раз при старте процесса, то есть, на скорость работы не влияет абсолютно. И самое главное - зачем все это? Какие преимущества это дает? Зачему тут динамический полиморфизм, когда все механизмы исходно статические? А всё это ради того, чтоб функция Exec была не статической. Тогда она сможет обращаться с членами класса-процесса. И можно будет сделать нормальную инкапсуляцию. Например, процесс LcdProcess будет иметь приватный член Lcd, и все остальные процессы будут писать в Lcd только посредством вызова методов класса LcdProcess. Сейчас для этого приходится заводить класс-посредник, типа LcdProxy. У меня практически все методы Exec выглядят как template <> OS_PROCESS void TTerminalProcess::Exec() { for (;;) terminal.loop(); } Я понимаю, что это мелочи, но напрягает. Наверняка есть ещё плюсы моего подхода, но я их пока не придумал:) Давай. Расскажешь потом. :) Проверил, всё замечательно работает:) Причём даже без изменения проектов. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jorikdima 0 30 марта, 2010 Опубликовано 30 марта, 2010 · Жалоба А всё это ради того, чтоб функция Exec была не статической. Тогда она сможет обращаться с членами класса-процесса. И можно будет сделать нормальную инкапсуляцию. Например, процесс LcdProcess будет иметь приватный член Lcd, и все остальные процессы будут писать в Lcd только посредством вызова методов класса LcdProcess. Вот мне в свое время тоже этого не хватало. Я хотел создать свой тип процесса, который работал бы с портом. То есть все остальные процессы лишь вызывают функцию передачи сообщения, а упаковкой данных в мой протокол, отправкой и приемом данных по UART занимается процесс. Прелесть в том, что я могу создать несколько таких однотиповых процессов, которые будут работать принципиально одинаково с разными портами, например. Это невозможно из-за статичности Exec() (из нее нельзя обращаться к нестатическим членам класса inher_process - наследника process). Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dxp 53 30 марта, 2010 Опубликовано 30 марта, 2010 · Жалоба Вроде я всё написал. В конструкторе TBaseProcess вместо адреса статического Exec() используется адрес глобальной функции start_process(). А она уже определяет текущий процесс и запускает его функцию Exec(). Вся эта возня происходит всего один раз при старте процесса, то есть, на скорость работы не влияет абсолютно. Во-первых, зачем функцию делать виртуальной? :) Обычная (просто в шаблоне объявить функцию-член, не в предке) не сойдет? Виртуальная функция потащит накладняки в виде vptr в каждом объекте + vtbl на каждый тип (т.е. тоже на каждый объект). Это мелочи, конечно. Но зачем? Во-вторых, вызов функции из start_process будет сохранять в стеке адрес возврата (и еще всякую служебную инфу в зависимости от платформы). С этим тоже можно с разным успехом побороться с помощью расширений типа __task (у IAR), но зачем? Ну, и в-третьих (см ниже)... А всё это ради того, чтоб функция Exec была не статической. Тогда она сможет обращаться с членами класса-процесса. И можно будет сделать нормальную инкапсуляцию. Например, процесс LcdProcess будет иметь приватный член Lcd, и все остальные процессы будут писать в Lcd только посредством вызова методов класса LcdProcess. Сейчас для этого приходится заводить класс-посредник, типа LcdProxy. У меня практически все методы Exec выглядят как template <> OS_PROCESS void TTerminalProcess::Exec() { for (;;) terminal.loop(); } Я понимаю, что это мелочи, но напрягает. Наверняка есть ещё плюсы моего подхода, но я их пока не придумал:) Тот вариант (который ты предложил) по сути является эквивалентным этому. Только там вызов terminal.loop() упрятан в недра. Это лучше? Сомневаюсь. Делать фукнцию Exec нестатической имеет смысл, если надо обращаться к представлению своего класса-процесса - т.е. к переменным priority, timeout, пулу памяти, в котором лежит стек процесса. На пользовательском уровне все это не нужно и даже наоборот вредно. Поэтому это и изолировано от юзера. Если уж ему очень надо, пусть пишет явный адрес объекта. Но это будет уже хак, и он сам явно нарушает правила, тем самым беря на себя ответственность. Его право. Но помогать ему в этом не следует. А статическая функция как раз очень хорошо согласуется с принципом - у процесса (объекта) может быть только одна единственная исполняемая функция, реализующая поток выполнения этого процесса. И следует четко осознать - функция-поток не является никаким пользовательским кодом. Это код ОС, которая предоставляет пользователю организовать свой код в псевдопараллельном виде и управляемый по событиям (event-driven). И поэтому надо четко отделять представление объекта-процесса от пользовательского кода, запускаемого из этого процесса. Т.ч. все логично. Вот мне в свое время тоже этого не хватало. Я хотел создать свой тип процесса, который работал бы с портом. То есть все остальные процессы лишь вызывают функцию передачи сообщения, а упаковкой данных в мой протокол, отправкой и приемом данных по UART занимается процесс. Прелесть в том, что я могу создать несколько таких однотиповых процессов, которые будут работать принципиально одинаково с разными портами, например. Это невозможно из-за статичности Exec() (из нее нельзя обращаться к нестатическим членам класса inher_process - наследника process). Не понял, чего вам не хватило. Не понял, зачем городить несколько однотипных процессов, ведь процесс - довольно ресурсоемкая штука, впору наоборот задуматься, как бы в один процесс упаковать несколько задач (если они выполняются в одном темпе). И самое главное, не понял, что бы вам дало, если бы Exec была бы нестатической? Ну, получили бы вы непосредственный доступ к преставлению объекта-процесса: protected: TStackItem* StackPointer; TTimeout Timeout; TPriority Priority; И что вам это дает? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 30 марта, 2010 Опубликовано 30 марта, 2010 · Жалоба Во-первых, зачем функцию делать виртуальной? :) Я бы и на обычную согласился, но не смог придумать, как её вызвать при старте процесс:) Во-вторых, вызов функции из start_process будет сохранять в стеке адрес возврата (и еще всякую служебную инфу в зависимости от платформы). С этим тоже можно с разным успехом побороться с помощью расширений типа __task (у IAR), но зачем? Это да, накладных расходов довольно много. Это меня немножко смущает. Но тут надо ещё поковыряться, я ведь просто демонстрировал концепцию, наверное можно проще. Ну, и в-третьих (см ниже)... Тот вариант (который ты предложил) по сути является эквивалентным этому. Только там вызов terminal.loop() упрятан в недра. Это лучше? Сомневаюсь. Ну, мне кажется, что так - стройнее, что ли. Есть процесс, есть его поля и методы. А не так, что есть процесс, а всё остальное (поля и методы) - в отдельном, другом объекте. Делать фукнцию Exec нестатической имеет смысл, если надо обращаться к представлению своего класса-процесса - т.е. к переменным priority, timeout, пулу памяти, в котором лежит стек процесса. Так с этого и началось:) Хотел добавить отладочные функции (размер стека, плюс (может быть) время активного состояния процесса, то, сё...). Без правки кода ОС. Вот и возникла мысль. Хотя конечно, при более внимательном рассмотрении видно, что это несколько противоречит идеологии ОС:) А статическая функция как раз очень хорошо согласуется с принципом - у процесса (объекта) может быть только одна единственная исполняемая функция, реализующая поток выполнения этого процесса. Как это? А если она вызовет другую? :) Ну и вообще, что статическая, что обычная функция при условии единственного экземпляра объекта - разницы практически никакой. (кроме возможности обращения к полям объекта) И следует четко осознать - функция-поток не является никаким пользовательским кодом. Это код ОС, которая предоставляет пользователю организовать свой код в псевдопараллельном виде и управляемый по событиям (event-driven). И поэтому надо четко отделять представление объекта-процесса от пользовательского кода, запускаемого из этого процесса. Т.ч. все логично. А вот тут - ни слова не понял:) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dxp 53 30 марта, 2010 Опубликовано 30 марта, 2010 · Жалоба Ну, мне кажется, что так - стройнее, что ли. Есть процесс, есть его поля и методы. А не так, что есть процесс, а всё остальное (поля и методы) - в отдельном, другом объекте. Стройнее оно было бы, если бы пользовательский код реально имел бы потребность в доступе к представлению (закрытой части) объекта-процесса. А такой потребности нет. Так с этого и началось:) Хотел добавить отладочные функции (размер стека, плюс (может быть) время активного состояния процесса, то, сё...). Без правки кода ОС. Вот и возникла мысль. Отладочный функционал надо разрабатывать и включать отдельно. Кстати, можно попробовать отнаследоваться от OS::process<> и там уже добавлять все, что душе угодно. Как это? А если она вызовет другую? :) Ну и вообще, что статическая, что обычная функция при условии единственного экземпляра объекта - разницы практически никакой. (кроме возможности обращения к полям объекта) Идеологически уже неверно. Рутовая функция должна быть одна. И статическая функция это обеспечивает в силу правил языка. В отличие от. А вот тут - ни слова не понял:) template<> TTerminalProcess::Exec() - это часть ОС, это код, обеспечиваемый осью особой функциональностью. Это не пользовательский код. Terminal.Loop() - это пользовательский код, написанный в соответствии с потребностями задачи. Не нужно смешивать эти две вещи - они хотя и "живут" вместе, дополняя друг друга, но тем не менее являются сущностями разного рода, что, в частности, проявляется в том, что у них нет у них потребности лазить к представлению друг друга. Такая вот идеология исходно заложена. Если хочется сделать процесс частью пользовательского кода, то как вариант попробовать, как уже сказано выше, отнаследоваться от process<> и родить свой класс. Но это уже другая идеология. И я пока не вижу каких-либо преимуществ у нее перед существующей. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 30 марта, 2010 Опубликовано 30 марта, 2010 · Жалоба Стройнее оно было бы, если бы пользовательский код реально имел бы потребность в доступе к представлению (закрытой части) объекта-процесса. А такой потребности нет. А отладка? :) Отладочный функционал надо разрабатывать и включать отдельно. Кстати, можно попробовать отнаследоваться от OS::process<> и там уже добавлять все, что душе угодно. Не выйдет. Ибо Exec() - не виртуальная. А именно её конструктор OS::process<> передаёт в конструктор TBaseProcess. Вернее, у меня получилось, но лишь путём полной замены OS::process<> на OS::debugprocess<> с переписыванием всего функционала:) Идеологически уже неверно. Рутовая функция должна быть одна. И статическая функция это обеспечивает в силу правил языка. В отличие от. Объект всё равно тоже должен быть один, это всяко нужно контролировать. Так что не суть. template<> TTerminalProcess::Exec() - это часть ОС, это код, обеспечиваемый осью особой функциональностью. Это не пользовательский код. Terminal.Loop() - это пользовательский код, написанный в соответствии с потребностями задачи. Не нужно смешивать эти две вещи - они хотя и "живут" вместе, дополняя друг друга, но тем не менее являются сущностями разного рода, что, в частности, проявляется в том, что у них нет у них потребности лазить к представлению друг друга. Не согласен. И то и то - пользовательский код. Я - пользователь, я завёл процесс TTerminalProcess, это мой, пользовательский процесс. И моё, пользователя, дело - писать весь код прямо в TTerminalProcess::Exec() или вынести его в отдельную функцию. От этого он не становится системным/пользовательским. А функция ОС - вызвать этот код и обеспечивать ему процессорные ресурсы. Такая вот идеология исходно заложена. Это-то я прекрасно понял:) Но ведь не вредно иногда порассуждать в ту или иную сторону от исходной идеологии? Может что-то придумается дельное:) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dxp 53 30 марта, 2010 Опубликовано 30 марта, 2010 · Жалоба А отладка? :) Про отладку писал выше - это надо функционал закладывать сразу в сорцы оси. И формализовать интерфейс управления этим делом. Остальное - костыли. Не выйдет. Ибо Exec() - не виртуальная. А именно её конструктор OS::process<> передаёт в конструктор TBaseProcess. Вернее, у меня получилось, но лишь путём полной замены OS::process<> на OS::debugprocess<> с переписыванием всего функционала:) Да, точно. И дело не в виртуальности, а в статичности. Не согласен. И то и то - пользовательский код. Я - пользователь, я завёл процесс TTerminalProcess, это мой, пользовательский процесс. И моё, пользователя, дело - писать весь код прямо в TTerminalProcess::Exec() или вынести его в отдельную функцию. От этого он не становится системным/пользовательским. А функция ОС - вызвать этот код и обеспечивать ему процессорные ресурсы. Ну, тогда вся ос - тоже пользовательский код. Критерий простой - Exec вызывается осью, значит это ее ресурс. А пользовательская функция вызывается пользователем - это его ресурс. И связи смысловой (чтобы нужно было обмениваться данными) меж ними нет. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
ReAl 0 30 марта, 2010 Опубликовано 30 марта, 2010 · Жалоба Стройнее оно было бы, если бы пользовательский код реально имел бы потребность в доступе к представлению (закрытой части) объекта-процесса. А такой потребности нет. ... Такая вот идеология исходно заложена. Если хочется сделать процесс частью пользовательского кода, то как вариант попробовать, как уже сказано выше, отнаследоваться от process<> и родить свой класс. Но это уже другая идеология. И я пока не вижу каких-либо преимуществ у нее перед существующей. У меня мысль "процесс как часть пользовательского кода" вытекает приблизительно из следующего: Ну вот есть "драйверный" класс для работы с аппаратурой. Есть у него функция-оработчик ISR, есть запрятанные внутрь мьютексы и, возможно, события, клиенты дргают за открытые функции. При этом они могут уснуть по мьютксу или по ожидаию события завершения работы, могут просто в очередь что-то поставить - не важно, реализация спрятана внутри. За .do() дёрнули и всё. Теперь что-то посложнее, или сам этот класс разросся, добавил функционала и теперь в модель "несколько функций + ISR" не укладывается, хочет процесс. Зачем менять модель работы других с этим делом? Ну стал тот же упомянутый lcd обслуживаться процессом - какое кому дело? Конечно, можно этому классу lcd в друзья прописать класс "его" процеса, чтобы тот мог использовать недоступные остальным поля да функции, скажем, все могут писать в очередь, а читать - только этот процесс. Или вообще использовать процесс всего лишь как обёртку над функцией выполнения реальной работы из класса, как у Антона. Но какой смысл в двух сущностях, когда она, вообще говоря, одна? Есть процесс, у него есть "системная" часть, есть связанная с его реальной работой. Всем, кроме системы, видна его "профессиональная деятельность", они к ней обращаются. Ну, мне кажется, что так - стройнее, что ли. Есть процесс, есть его поля и методы. А не так, что есть процесс, а всё остальное (поля и методы) - в отдельном, другом объекте.Ну вот что-то такое (процессы как наследники базового процесса и нестатическая функция процесса) и я пытался обсуждать. Хотел причесать думалки и что-то родить для более предметного обсуждения, но заглох - сейчас сам не могу понять, чем занимаюсь, а о сферических конях не хотелось говорить. Можно ведь и конструктор процесса так организовать, чтобы он в подготавливаемый для первого "вызова" стековый кадр затолкал и саму функцию Exec(), и необходимый для неё this. В результате, как минимум, обращения к "системным" и "пользовательским" полям процесса смогут идти относительно одного указателя. Что где ещё вылезет - надо смотреть. Я хотел сначала вникнуть в ту зависшую переделку с TService, потом об этом подумать предметнее, но так и завис :-( Про отладку писал выше - это надо функционал закладывать сразу в сорцы оси. И формализовать интерфейс управления этим делом. Остальное - костыли.+1 И, возможно, начать с ( {от|в}ключаемого defin-ом или параметром шаблона ) контроля стека - опять таки, пришлось скоприровать шаблон процесса под другим именем и добавить в него такой контроль. Да, точно. И дело не в виртуальности, а в статичности.Ага. Не помню уже - с тобой это в аське пару лет назад обсуждал, или на ком-то другом потренировалася, а потом решил не трясти воздух недодуманными мыслями. Критерий простой - Exec вызывается осью, значит это ее ресурс.Похоже, таки с тобой :-) Да, взывается осью, и эта Exec() как-то очень похожа на isr() из "класса-драйвера", вызывающуюся аппаратурой. Но ведь остальные функции класса-драйвера находятся в нём же. И мне эти вещи (класс-"драйвер" и процесс) кажутся подобными, хоть и на разнх уровнях. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
ReAl 0 30 марта, 2010 Опубликовано 30 марта, 2010 · Жалоба --------------- Тьху, вспомнил - с заталкиванием в образ стекового кадра this и указателя на нестатическую функцию проблема - указатель на функцию-член это не просто указатель на функцию, там и размер другой. Ох уж этот склероз... ... class TBaseProcess { ... static void start_process(TBaseProcess* proc) }; void TBaseProcess::start_process(TBaseProcess* proc) { proc->Exec(); } TBaseProcess::TBaseProcess(TStackItem* Stack, TPriority pr) { Kernel.RegisterProcess(this); ... // на место нужного регистра затолкать reinterpret_cast<dword>(this) *(--StackPointer) = reinterpret_cast<dword>(start_process); Но всё равно лишний расход стека будет. А вот сразу Exec сюда - увы... Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dxp 53 31 марта, 2010 Опубликовано 31 марта, 2010 · Жалоба У меня мысль "процесс как часть пользовательского кода" вытекает приблизительно из следующего: <...> Похоже, таки с тобой :-) Да, взывается осью, и эта Exec() как-то очень похожа на isr() из "класса-драйвера", вызывающуюся аппаратурой. Но ведь остальные функции класса-драйвера находятся в нём же. И мне эти вещи (класс-"драйвер" и процесс) кажутся подобными, хоть и на разнх уровнях. Аналогия, все же, другая. ISR - сам по себе просто функция, которая целиком отдана на откуп пользователю. У нее нет никакого внутреннего представления. Сделать ее частью пользовательского класса имеет смысл потому, что она может "хотеть" иметь доступ к представлению (закрытой части класса) своего класса. Это и является критерием, почему ISR - это часть пользовательского кода. А процесс - это часть ОС, в доступе к его представлению пользовательский код нужды не имеет. И сам процесс не имеет никакой нужды в доступе к потрохам пользовательского кода (классам). Поэтому это сущности ортогональные. Другая аналогия. Системная функция main - она системный код или пользовательский? Именно сама функция, а не ее потроха. Функция - системная, ее система вызывает, и пользовательский код никак не управляет ее вызовом. Хотя использует ее в своих нуждах. Ровно так же и наши процессы. Если уж хочется держать процесс вместе с кодом, который его использует, то можно попробовать объект-процесс заводить не в глобальной области видимости, а как член пользовательского класса. Главное, чтобы этот класс был сам в global scope и имел глобальное же время жизни. Ну, и не забыть конструктор процесса вызвать при вызове конструктора объемлющего класса. Я так не делал еще, но на вскидку не вижу препятствий. Это, имхо, будет примерно то, что ты хочешь. :) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 31 марта, 2010 Опубликовано 31 марта, 2010 · Жалоба Про отладку писал выше - это надо функционал закладывать сразу в сорцы оси. И формализовать интерфейс управления этим делом. Остальное - костыли. Дык! Давайте займёмся. Я так понимаю, почти у каждого уже есть свой вариант. Осталось только выбрать самый-самый:) Ну, тогда вся ос - тоже пользовательский код. Критерий простой - Exec вызывается осью, значит это ее ресурс. А пользовательская функция вызывается пользователем - это его ресурс. И связи смысловой (чтобы нужно было обмениваться данными) меж ними нет. Опять не согласен. Все функции в конечном счёте вызываются осью. Критерий - кто написал функцию. Пользователь написал - пользовательская функция. А к чему мы это вообще? :) TBaseProcess::TBaseProcess(TStackItem* Stack, TPriority pr) { Kernel.RegisterProcess(this); ... // на место нужного регистра затолкать reinterpret_cast<dword>(this) *(--StackPointer) = reinterpret_cast<dword>(start_process); Но всё равно лишний расход стека будет. А вот сразу Exec сюда - увы... Да, так красивше:) И - да, дополнительный расход стека всё равно есть. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dxp 53 31 марта, 2010 Опубликовано 31 марта, 2010 · Жалоба Дык! Давайте займёмся. Я так понимаю, почти у каждого уже есть свой вариант. Осталось только выбрать самый-самый:) Постепенно сделаем. Для начала надо определить, что именно нужно мониторить при отладке. Перечень. Второе - канал для мониторинга (терминалка или что еще). Можно в группе тему поднять. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 132 31 марта, 2010 Опубликовано 31 марта, 2010 · Жалоба Постепенно сделаем. Для начала надо определить, что именно нужно мониторить при отладке. Перечень. Второе - канал для мониторинга (терминалка или что еще).Канал может быть абстрактным классом. Пользователь сам допишет. А вот мониторить нужно, мне кажется: а) расход процессорного времени по процессам б) в ожидании какого сервиса уснул процесс в) расход стека. Кому надо что-то еще - дописываем. Причем надо иметь возможность подключать/отключать каждый из пунктов на этапе компиляции. пункт б) я использую (правда без вывода, смотрю отладчиком), очень помогает при выяснении "какого черта этот процесс повис". пункта а) очень не хватает. Можно в группе тему поднять.Да как-то кисло там. Сообщения то доходят на почту, то теряются. И тут людей больше. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 31 марта, 2010 Опубликовано 31 марта, 2010 · Жалоба Канал может быть абстрактным классом. Пользователь сам допишет. Канал вообще не нужен, имхо. Нужны функции для получения требуемых данных, типа TBaseProcess.GetFreeStack(), TBaseProcess.GetTimes()... етц. А уж как их вызовет пользователь - его дело. Да как-то кисло там. Сообщения то доходят на почту, то теряются. И тут людей больше. +1. Ещё и с русскими сообщениями стала какая-то ерунда происходить. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться