Перейти к содержанию
    

Ну а почему нет? :) Зачем заранее рубить возможные расширения? Это ж не винда:) Да и винду расковыряли, несмотря ни на что.

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

 

Дык, элементарно:

Э-э... А запускать ты их откуда собрался? А как тут все это вяжется со стековым кадром? И самое главное - зачем все это? Какие преимущества это дает? Зачему тут динамический полиморфизм, когда все механизмы исходно статические?

 

Имхо, весьма элегантно. Может даже и выигрыш есть... Завтра попробую в работе:)

Давай. Расскажешь потом. :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Ура :)

 

Э-э... А запускать ты их откуда собрался? А как тут все это вяжется со стековым кадром?

Вроде я всё написал. В конструкторе TBaseProcess вместо адреса статического Exec() используется адрес глобальной функции start_process(). А она уже определяет текущий процесс и запускает его функцию Exec(). Вся эта возня происходит всего один раз при старте процесса, то есть, на скорость работы не влияет абсолютно.

И самое главное - зачем все это? Какие преимущества это дает? Зачему тут динамический полиморфизм, когда все механизмы исходно статические?

А всё это ради того, чтоб функция Exec была не статической. Тогда она сможет обращаться с членами класса-процесса. И можно будет сделать нормальную инкапсуляцию. Например, процесс LcdProcess будет иметь приватный член Lcd, и все остальные процессы будут писать в Lcd только посредством вызова методов класса LcdProcess.

Сейчас для этого приходится заводить класс-посредник, типа LcdProxy. У меня практически все методы Exec выглядят как

    template <>
    OS_PROCESS void TTerminalProcess::Exec()
    {
        for (;;)
            terminal.loop();
    }

Я понимаю, что это мелочи, но напрягает. Наверняка есть ещё плюсы моего подхода, но я их пока не придумал:)

 

Давай. Расскажешь потом. :)

 

Проверил, всё замечательно работает:) Причём даже без изменения проектов.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А всё это ради того, чтоб функция Exec была не статической. Тогда она сможет обращаться с членами класса-процесса. И можно будет сделать нормальную инкапсуляцию. Например, процесс LcdProcess будет иметь приватный член Lcd, и все остальные процессы будут писать в Lcd только посредством вызова методов класса LcdProcess.

Вот мне в свое время тоже этого не хватало. Я хотел создать свой тип процесса, который работал бы с портом. То есть все остальные процессы лишь вызывают функцию передачи сообщения, а упаковкой данных в мой протокол, отправкой и приемом данных по UART занимается процесс. Прелесть в том, что я могу создать несколько таких однотиповых процессов, которые будут работать принципиально одинаково с разными портами, например. Это невозможно из-за статичности Exec() (из нее нельзя обращаться к нестатическим членам класса inher_process - наследника process).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вроде я всё написал. В конструкторе 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;

 

И что вам это дает?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Во-первых, зачем функцию делать виртуальной? :)

 

Я бы и на обычную согласился, но не смог придумать, как её вызвать при старте процесс:)

 

Во-вторых, вызов функции из start_process будет сохранять в стеке адрес возврата (и еще всякую служебную инфу в зависимости от платформы). С этим тоже можно с разным успехом побороться с помощью расширений типа __task (у IAR), но зачем?

 

Это да, накладных расходов довольно много. Это меня немножко смущает. Но тут надо ещё поковыряться, я ведь просто демонстрировал концепцию, наверное можно проще.

 

Ну, и в-третьих (см ниже)...

Тот вариант (который ты предложил) по сути является эквивалентным этому. Только там вызов terminal.loop() упрятан в недра. Это лучше? Сомневаюсь.

 

Ну, мне кажется, что так - стройнее, что ли. Есть процесс, есть его поля и методы. А не так, что есть процесс, а всё остальное (поля и методы) - в отдельном, другом объекте.

 

Делать фукнцию Exec нестатической имеет смысл, если надо обращаться к представлению своего класса-процесса - т.е. к переменным priority, timeout, пулу памяти, в котором лежит стек процесса.

 

Так с этого и началось:) Хотел добавить отладочные функции (размер стека, плюс (может быть) время активного состояния процесса, то, сё...). Без правки кода ОС. Вот и возникла мысль.

Хотя конечно, при более внимательном рассмотрении видно, что это несколько противоречит идеологии ОС:)

 

А статическая функция как раз очень хорошо согласуется с принципом - у процесса (объекта) может быть только одна единственная исполняемая функция, реализующая поток выполнения этого процесса.

 

Как это? А если она вызовет другую? :) Ну и вообще, что статическая, что обычная функция при условии единственного экземпляра объекта - разницы практически никакой. (кроме возможности обращения к полям объекта)

 

И следует четко осознать - функция-поток не является никаким пользовательским кодом. Это код ОС, которая предоставляет пользователю организовать свой код в псевдопараллельном виде и управляемый по событиям (event-driven). И поэтому надо четко отделять представление объекта-процесса от пользовательского кода, запускаемого из этого процесса. Т.ч. все логично.

 

А вот тут - ни слова не понял:)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну, мне кажется, что так - стройнее, что ли. Есть процесс, есть его поля и методы. А не так, что есть процесс, а всё остальное (поля и методы) - в отдельном, другом объекте.

Стройнее оно было бы, если бы пользовательский код реально имел бы потребность в доступе к представлению (закрытой части) объекта-процесса. А такой потребности нет.

 

 

Так с этого и началось:) Хотел добавить отладочные функции (размер стека, плюс (может быть) время активного состояния процесса, то, сё...). Без правки кода ОС. Вот и возникла мысль.

Отладочный функционал надо разрабатывать и включать отдельно. Кстати, можно попробовать отнаследоваться от OS::process<> и там уже добавлять все, что душе угодно.

 

 

Как это? А если она вызовет другую? :) Ну и вообще, что статическая, что обычная функция при условии единственного экземпляра объекта - разницы практически никакой. (кроме возможности обращения к полям объекта)

Идеологически уже неверно. Рутовая функция должна быть одна. И статическая функция это обеспечивает в силу правил языка. В отличие от.

 

А вот тут - ни слова не понял:)

template<> TTerminalProcess::Exec() - это часть ОС, это код, обеспечиваемый осью особой функциональностью. Это не пользовательский код.

 

Terminal.Loop() - это пользовательский код, написанный в соответствии с потребностями задачи.

 

Не нужно смешивать эти две вещи - они хотя и "живут" вместе, дополняя друг друга, но тем не менее являются сущностями разного рода, что, в частности, проявляется в том, что у них нет у них потребности лазить к представлению друг друга.

 

Такая вот идеология исходно заложена. Если хочется сделать процесс частью пользовательского кода, то как вариант попробовать, как уже сказано выше, отнаследоваться от process<> и родить свой класс. Но это уже другая идеология. И я пока не вижу каких-либо преимуществ у нее перед существующей.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Стройнее оно было бы, если бы пользовательский код реально имел бы потребность в доступе к представлению (закрытой части) объекта-процесса. А такой потребности нет.

 

А отладка? :)

 

Отладочный функционал надо разрабатывать и включать отдельно. Кстати, можно попробовать отнаследоваться от OS::process<> и там уже добавлять все, что душе угодно.

 

Не выйдет. Ибо Exec() - не виртуальная. А именно её конструктор OS::process<> передаёт в конструктор TBaseProcess.

Вернее, у меня получилось, но лишь путём полной замены OS::process<> на OS::debugprocess<> с переписыванием всего функционала:)

 

Идеологически уже неверно. Рутовая функция должна быть одна. И статическая функция это обеспечивает в силу правил языка. В отличие от.

 

Объект всё равно тоже должен быть один, это всяко нужно контролировать. Так что не суть.

 

template<> TTerminalProcess::Exec() - это часть ОС, это код, обеспечиваемый осью особой функциональностью. Это не пользовательский код.

 

Terminal.Loop() - это пользовательский код, написанный в соответствии с потребностями задачи.

 

Не нужно смешивать эти две вещи - они хотя и "живут" вместе, дополняя друг друга, но тем не менее являются сущностями разного рода, что, в частности, проявляется в том, что у них нет у них потребности лазить к представлению друг друга.

 

Не согласен. И то и то - пользовательский код. Я - пользователь, я завёл процесс TTerminalProcess, это мой, пользовательский процесс. И моё, пользователя, дело - писать весь код прямо в TTerminalProcess::Exec() или вынести его в отдельную функцию. От этого он не становится системным/пользовательским. А функция ОС - вызвать этот код и обеспечивать ему процессорные ресурсы.

 

Такая вот идеология исходно заложена.

 

Это-то я прекрасно понял:) Но ведь не вредно иногда порассуждать в ту или иную сторону от исходной идеологии? Может что-то придумается дельное:)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А отладка? :)

Про отладку писал выше - это надо функционал закладывать сразу в сорцы оси. И формализовать интерфейс управления этим делом. Остальное - костыли.

 

Не выйдет. Ибо Exec() - не виртуальная. А именно её конструктор OS::process<> передаёт в конструктор TBaseProcess.

Вернее, у меня получилось, но лишь путём полной замены OS::process<> на OS::debugprocess<> с переписыванием всего функционала:)

Да, точно. И дело не в виртуальности, а в статичности.

 

Не согласен. И то и то - пользовательский код. Я - пользователь, я завёл процесс TTerminalProcess, это мой, пользовательский процесс. И моё, пользователя, дело - писать весь код прямо в TTerminalProcess::Exec() или вынести его в отдельную функцию. От этого он не становится системным/пользовательским. А функция ОС - вызвать этот код и обеспечивать ему процессорные ресурсы.

Ну, тогда вся ос - тоже пользовательский код. Критерий простой - Exec вызывается осью, значит это ее ресурс. А пользовательская функция вызывается пользователем - это его ресурс. И связи смысловой (чтобы нужно было обмениваться данными) меж ними нет.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Стройнее оно было бы, если бы пользовательский код реально имел бы потребность в доступе к представлению (закрытой части) объекта-процесса. А такой потребности нет.

...

Такая вот идеология исходно заложена. Если хочется сделать процесс частью пользовательского кода, то как вариант попробовать, как уже сказано выше, отнаследоваться от process<> и родить свой класс. Но это уже другая идеология. И я пока не вижу каких-либо преимуществ у нее перед существующей.

У меня мысль "процесс как часть пользовательского кода" вытекает приблизительно из следующего:

Ну вот есть "драйверный" класс для работы с аппаратурой. Есть у него функция-оработчик ISR, есть запрятанные внутрь мьютексы и, возможно, события, клиенты дргают за открытые функции. При этом они могут уснуть по мьютксу или по ожидаию события завершения работы, могут просто в очередь что-то поставить - не важно, реализация спрятана внутри. За .do() дёрнули и всё.

Теперь что-то посложнее, или сам этот класс разросся, добавил функционала и теперь в модель "несколько функций + ISR" не укладывается, хочет процесс. Зачем менять модель работы других с этим делом? Ну стал тот же упомянутый lcd обслуживаться процессом - какое кому дело?

Конечно, можно этому классу lcd в друзья прописать класс "его" процеса, чтобы тот мог использовать недоступные остальным поля да функции, скажем, все могут писать в очередь, а читать - только этот процесс. Или вообще использовать процесс всего лишь как обёртку над функцией выполнения реальной работы из класса, как у Антона. Но какой смысл в двух сущностях, когда она, вообще говоря, одна?

Есть процесс, у него есть "системная" часть, есть связанная с его реальной работой. Всем, кроме системы, видна его "профессиональная деятельность", они к ней обращаются.

 

Ну, мне кажется, что так - стройнее, что ли. Есть процесс, есть его поля и методы. А не так, что есть процесс, а всё остальное (поля и методы) - в отдельном, другом объекте.
Ну вот что-то такое (процессы как наследники базового процесса и нестатическая функция процесса) и я пытался обсуждать.

Хотел причесать думалки и что-то родить для более предметного обсуждения, но заглох - сейчас сам не могу понять, чем занимаюсь, а о сферических конях не хотелось говорить.

Можно ведь и конструктор процесса так организовать, чтобы он в подготавливаемый для первого "вызова" стековый кадр затолкал и саму функцию Exec(), и необходимый для неё this.

В результате, как минимум, обращения к "системным" и "пользовательским" полям процесса смогут идти относительно одного указателя. Что где ещё вылезет - надо смотреть.

Я хотел сначала вникнуть в ту зависшую переделку с TService, потом об этом подумать предметнее, но так и завис :-(

 

Про отладку писал выше - это надо функционал закладывать сразу в сорцы оси. И формализовать интерфейс управления этим делом. Остальное - костыли.
+1

И, возможно, начать с ( {от|в}ключаемого defin-ом или параметром шаблона ) контроля стека - опять таки, пришлось скоприровать шаблон процесса под другим именем и добавить в него такой контроль.

 

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

 

Критерий простой - Exec вызывается осью, значит это ее ресурс.
Похоже, таки с тобой :-)

Да, взывается осью, и эта Exec() как-то очень похожа на isr() из "класса-драйвера", вызывающуюся аппаратурой.

Но ведь остальные функции класса-драйвера находятся в нём же.

И мне эти вещи (класс-"драйвер" и процесс) кажутся подобными, хоть и на разнх уровнях.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

---------------

Тьху, вспомнил - с заталкиванием в образ стекового кадра 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 сюда - увы...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

У меня мысль "процесс как часть пользовательского кода" вытекает приблизительно из следующего:

<...>

Похоже, таки с тобой :-)

Да, взывается осью, и эта Exec() как-то очень похожа на isr() из "класса-драйвера", вызывающуюся аппаратурой.

Но ведь остальные функции класса-драйвера находятся в нём же.

И мне эти вещи (класс-"драйвер" и процесс) кажутся подобными, хоть и на разнх уровнях.

Аналогия, все же, другая. ISR - сам по себе просто функция, которая целиком отдана на откуп пользователю. У нее нет никакого внутреннего представления. Сделать ее частью пользовательского класса имеет смысл потому, что она может "хотеть" иметь доступ к представлению (закрытой части класса) своего класса. Это и является критерием, почему ISR - это часть пользовательского кода. А процесс - это часть ОС, в доступе к его представлению пользовательский код нужды не имеет. И сам процесс не имеет никакой нужды в доступе к потрохам пользовательского кода (классам). Поэтому это сущности ортогональные.

 

Другая аналогия. Системная функция main - она системный код или пользовательский? Именно сама функция, а не ее потроха. Функция - системная, ее система вызывает, и пользовательский код никак не управляет ее вызовом. Хотя использует ее в своих нуждах. Ровно так же и наши процессы.

 

Если уж хочется держать процесс вместе с кодом, который его использует, то можно попробовать объект-процесс заводить не в глобальной области видимости, а как член пользовательского класса. Главное, чтобы этот класс был сам в global scope и имел глобальное же время жизни. Ну, и не забыть конструктор процесса вызвать при вызове конструктора объемлющего класса. Я так не делал еще, но на вскидку не вижу препятствий. Это, имхо, будет примерно то, что ты хочешь. :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Про отладку писал выше - это надо функционал закладывать сразу в сорцы оси. И формализовать интерфейс управления этим делом. Остальное - костыли.

 

Дык! Давайте займёмся. Я так понимаю, почти у каждого уже есть свой вариант. Осталось только выбрать самый-самый:)

 

Ну, тогда вся ос - тоже пользовательский код. Критерий простой - Exec вызывается осью, значит это ее ресурс. А пользовательская функция вызывается пользователем - это его ресурс. И связи смысловой (чтобы нужно было обмениваться данными) меж ними нет.

 

Опять не согласен. Все функции в конечном счёте вызываются осью. Критерий - кто написал функцию. Пользователь написал - пользовательская функция.

А к чему мы это вообще? :)

 

TBaseProcess::TBaseProcess(TStackItem* Stack, TPriority pr)
{
    Kernel.RegisterProcess(this);
...
    // на место нужного регистра затолкать reinterpret_cast<dword>(this)
    *(--StackPointer)  = reinterpret_cast<dword>(start_process);

Но всё равно лишний расход стека будет. А вот сразу Exec сюда - увы...

 

Да, так красивше:) И - да, дополнительный расход стека всё равно есть.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Дык! Давайте займёмся. Я так понимаю, почти у каждого уже есть свой вариант. Осталось только выбрать самый-самый:)

Постепенно сделаем. Для начала надо определить, что именно нужно мониторить при отладке. Перечень. Второе - канал для мониторинга (терминалка или что еще). Можно в группе тему поднять.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Постепенно сделаем. Для начала надо определить, что именно нужно мониторить при отладке. Перечень. Второе - канал для мониторинга (терминалка или что еще).
Канал может быть абстрактным классом. Пользователь сам допишет.

А вот мониторить нужно, мне кажется:

а) расход процессорного времени по процессам

б) в ожидании какого сервиса уснул процесс

в) расход стека.

Кому надо что-то еще - дописываем.

Причем надо иметь возможность подключать/отключать каждый из пунктов на этапе компиляции.

пункт б) я использую (правда без вывода, смотрю отладчиком), очень помогает при выяснении "какого черта этот процесс повис".

пункта а) очень не хватает.

 

Можно в группе тему поднять.
Да как-то кисло там. Сообщения то доходят на почту, то теряются. И тут людей больше.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Канал может быть абстрактным классом. Пользователь сам допишет.

 

Канал вообще не нужен, имхо. Нужны функции для получения требуемых данных, типа

TBaseProcess.GetFreeStack(), TBaseProcess.GetTimes()... етц. А уж как их вызовет пользователь - его дело.

 

Да как-то кисло там. Сообщения то доходят на почту, то теряются. И тут людей больше.

 

+1. Ещё и с русскими сообщениями стала какая-то ерунда происходить.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...