Важная информация

User Tag List

Страница 1 из 3 123 ПоследняяПоследняя
Показано с 1 по 10 из 30

Тема: Вытесняющая многозадачность (диспетчер mzkernel)

  1. #1
    Master
    Регистрация
    08.05.2007
    Адрес
    Dnepropetrovsk
    Сообщений
    880
    Благодарностей: 470
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Cool Вытесняющая многозадачность (диспетчер mzkernel)

    Здравствуйте, друзья.

    Предлагаю вашему вниманию диспетчер для организации на ZX Spectrum вытесняющей многозадачной среды.

    Описание его архитектуры и работы - на Хабре.

    Текущая версия исходника - на GitHub

    На данный момент реализация минимальная. Фиксированное количество потоков (threads), фиксированный приоритет потоков, из объектов ожидания присутствует только два типа Events. Этого уже достаточно для того, чтобы можно было говорить о многозадачности, но мало для настоящей ОС. Можно использовать только в рамках отдельной прикладной программы, где заранее известно, сколько надо потоков.

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

    Приглашаю к обсуждению и внесению предложений по усовершенствованию, оптимизации. Сам планирую на досуге вносить усовершенствования, в первую очередь, следующего плана:
    1) динамический приоритет потоков
    2) объекты ожидания типа Mutex
    3) пуск новых и завершение старых потоков реализовано 21.07.2014

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

    Обновления:
    21.07.2014 - небольшие оптимизации, добавлена возможность пуска новых и завершения старых потоков
    Последний раз редактировалось Barmaley_m; 22.07.2014 в 02:43. Причина: обновление версии от 21.07.2014

  2. Эти 12 пользователя(ей) поблагодарили Barmaley_m за это полезное сообщение:
    alone (04.07.2014), AzAtom (11.08.2016), EARL (18.07.2014), Eltaron (04.07.2014), mastermind (09.07.2014), Sergey (07.07.2014), SfS (09.07.2014), Valen (15.07.2014), Vitamin (04.07.2014), Vslav (22.07.2014), Граф Куракин (11.07.2014), Максагор (12.07.2014)

  3. #1
    С любовью к вам, Yandex.Direct
    Размещение рекламы на форуме способствует его дальнейшему развитию

  4. #2
    Guru
    Регистрация
    03.01.2006
    Адрес
    Рязань
    Сообщений
    2,935
    Благодарностей: 1071
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Может быть, поможет такой проект ядра с поддержкой страничной адресации и сообщений (рассчитан на ассемблер ALASM), автор sman:

    Код:
    STRUCT app
    BYTE priority ;приоритет (0=конец списка)
    BYTE flags ;флаги (factive, ftimeslice, fusetimer)
    factive=0 ;есть сообщения: SET при добавлении сообщения, RES при взятии последнего сообщения
    ftimeslice=1
    fusetimer=2
    ;ftimer=3
    ;fcritical=4 (чтобы не портить hl)
    WORD id ;номер задачи
    WORD mainpg ;главная страница задачи (там очередь сообщений)
    WORD curmsg ;адрес текущего сообщения этой задаче
    WORD endmsg ;адрес конца очереди сообщений этой задаче
    WORD curpg ;текущая страница
    WORD sp ;текущий адрес стека (можно хранить в ТЕКУЩЕЙ (не главной) странице, но это бессмысленно)
    WORD next ;указатель на следущую задачу (следующая за выполняемой внутри того же приоритета)
    ;[номер задачи, которая ждёт готовности этой задачи]
    ENDSTRUCT
    
    STRUCT msg
    WORD type ;тип сообщения (2 б)
    WORD sender ;автор сообщения (2 б)
    WORD par0
    WORD par1
    WORD par2
    WORD par3
    WORD par4
    WORD par5
    ENDSTRUCT
    ;16 байт (делитель числа 256)
    
    WORD Os_curapp ;()=Os_Tapp
    WORD Os_nextapp ;()=Os_Tapp
    WORD Isr_SP
    WORD Os_frames
    BYTE Os_critical
    WORD Os_targetid
    ARRAY Os_msg1,SIZEOF_msg
    ARRAY Os_msg2,SIZEOF_msg
    Os_TIMERSENDER=55555
    
    MACRO STARTCRITICAL
    ld hl,Os_critical
    inc (hl) ;с этого момента уже не задача, а шедулер
    ENDM
    
    MACRO ENDCRITICAL
    ld hl,Os_critical
    dec (hl) ;выключение+проверка прерванности ОДНОВРЕМЕННО
    CALL NZ,IsrEndCritical ;переход на IsrEndCritical не прервется, т.к. в этом случае di
    ;тут уже можно считать, что выполняется задача
    ENDM
    
    ;выход из контекста задачи
    MACRO RELEASECONTEXT
    ld ix,(Os_curapp)
    ld hl,(curpg)
    PUTWORD app_curpg,HL
    ld hl,0
    add hl,sp
    PUTWORD app_sp,HL
    ld sp,lowmem
    ENDM
    
    ;вход в контекст задачи
    MACRO GETCONTEXT
    GETWORD HL,app_sp
    push bc,hl
    GETWORD HL,app_curpg
    SETPG
    pop hl,bc
    ld sp,hl
    ENDM
    
    MACRO SETFLAG
    SET \0,(IX+app_flags)
    ENDM
    
    MACRO RESFLAG
    RES \0,(IX+app_flags)
    ENDM
    
    MACRO TESTFLAG
    BIT \0,(IX+app_flags)
    ENDM
    
    
    ;шлет событие клавиатуре, гую, мыши, плееру (всем, кто подписан на таймер)
    ;подписанность на таймер - флажок fusetimer (как программа его установит?)
    ;страница на выходе должна должна остаться прежней!
    ;просто поставить флаг ftimer
    ;[вариант:вызвать обработчик таймера у конкретного потока (если задан адрес)]
    Timer
    ld ix,Os_Tapp-SIZEOF_app
    ld de,SIZEOF_app
    Timer0
    add ix,de
    ld a,(ix+app_priority)
    or a
    ret z
    TESTFLAG fusetimer
    jz Timer0
    SETFLAG ftimer
    jr Timer0
    
    
    
    ;делает то же, что таймслайс
    Sleep
    STARTCRITICAL
    ld ix,(Os_curapp)
    SETFLAG ftimeslice ;в контексте задачи
    call Sched ;переключить задачу (стек и т.п.)
    ld ix,(Os_curapp)
    RESFLAG ftimeslice ;в контексте задачи
    ENDCRITICAL
    ;выход из таймслайса
    ret
    
    IsrEndCritical
    ;прерывания отключены
    ;можно портить только AF,HL
    dec (hl) ;1-1=0
    push ...
    call Isr
    pop ...
    ;прерывания включены
    ret
    
    Isr
    ;регистры можно портить, но страница должна быть включена та же
    ld (Isr_SP),sp
    ld sp,lowmem
    ;определяем тип прерывания и работаем с ним (портит списки задач и сообщений)
    ;Если не определилось RS232, то экранное.
    ;Если определилось RS232, читать ВЕСЬ буфер (т.к. могло потеряться прерывание от RS232).
    ;Если RS232 произошло чуть позже экранного, то оно теряется.
    ;Если экранное произошло чуть позже RS232, то оно теряется (с этим ничего не поделать)
    ;fcritical=0, т.е. можно вызывать SENDMSG (но нельзя DOMSG)
    call Timer
    ;
    ld sp,(Isr_SP)
    ei
    ret
    
    ;случай прерванной критической секции
    ;сразу возврат, а перед выходом из критической секции вызвать IsrEndCritical
    CriticalInt
    ;прерывания выключены
    inc a ;=2
    ld (Os_critical),a
    ;pop hl
    pop af
    ret ;в шедулер
    
    OnInt
    push af
    ;push hl
    ;todo внутри timer
    ;ld hl,(Os_frames)
    ;inc hl
    ;ld (Os_frames),hl
    ld a,(Os_critical)
    or a
    jnz CriticalInt
    push ...
    CALL Isr ;делает ei
    ;включен контекст задачи, ее стек и ее страница, т.е. якобы она выполняется
    call Sleep
    pop ...
    ;pop hl
    pop af
    ret
    
    GetMsg
    STARTCRITICAL
    call Sched
    ;exx
    ;todo при вызове Sched регистры класть сюда, а брать их на выходе (а не здесь)?
    ld bc,(Os_msg1+msg_par3)
    ld de,(Os_msg1+msg_par4)
    ld hl,(Os_msg1+msg_par5)
    exx
    ld bc,(Os_msg1+msg_par0)
    ld de,(Os_msg1+msg_par1)
    ld iy,(Os_msg1+msg_par2)
    ;парамеры сообщения в hl',de',bc',iy,de,bc
    ld hl,(Os_msg1+msg_type) ;обязательно в критической секции!
    ld ix,(Os_msg1+msg_sender) ;
    push hl
    ENDCRITICAL
    pop hl
    ret
    
    ;парамеры сообщения взять в bc,de,iy,bc',de',hl'
    MsgGetPars
    GETWORD HL,app_mainpg
    SETPG
    GETWORD HL,app_curmsg
    ld de,Os_msg1
    ld bc,15
    ldir
    ld a,(hl)
    ld (de),a
    inc l
    PUTWORD app_curmsg,HL
    GETWORD DE,app_endmsg
    or a
    sbc hl,de
    ret nz ;не последнее сообщение в очереди
    RESFLAG factive ;последнее
    ret
    
    ;ищем первую активную/таймслайсную задачу от начала списка задач
    ;активная - либо в таймслайсе, либо есть сообщения, либо таймер
    SchedFind
    LOCAL
    ld ix,Os_Tapp-SIZEOF_app
    ld de,(Os_nextapp) ;"следующая внутри приоритета"
    ld bc,SIZEOF_app
    _next
    add ix,bc
    ld a,(ix+app_flags)
    and factive|ftimeslice|ftimer
    jz _next ;имеет тот же приоритет, что "следующая внутри приоритета", но расположена раньше - пропускаем
    ld a,(de) ;приоритет текущей/следующей внутри приоритета
    cp (ix+app_priority)
    jnz _ok
    push ix
    pop hl
    ;or a ;CY=0
    sbc hl,de
    jc _next ;расположена раньше внутри приоритета - пропускаем
    _ok
    GETWORD HL,app_next
    ld (Os_nextapp),hl ;"следующая внутри приоритета"
    ENDL
    ret
    
    ;переход на задачу с ID=bc
    CallTask
    STARTCRITICAL
    RELEASECONTEXT ;портит hl,ix
    ld ix,(Os_curapp)
    SETFLAG ftimeslice ;в критической секции, а то вдруг до STARTCRITICAL выпадет прерывание, которое завалит задачу в таймслайс и на следующем входе в неё СНИМЕТ этот флаг
    ;поиск задачи (если нету такой, то любую)
    ld ix,Os_Tapp-SIZEOF_app
    ld de,SIZEOF_app
    SchedGivenSearch
    add ix,de
    ld a,(ix+app_priority)
    or a
    jz SchedSearch ;нету такой задачи - переходим на любую
    GETWORD HL,app_id
    ;CY=0
    sbc hl,bc
    jnz SchedGivenSearch
    ;не следует искать Os_nextapp, т.к. мы рвём последовательность выполнения задач только для одного вызова
    jr SchedFound
    
    Sched
    RELEASECONTEXT ;портит hl,ix
    SchedSearch
    call SchedFind ;выбираем задачу - результат в ix
    SchedFound
    TESTFLAG ftimeslice
    jnz SchedOk ;таймслайс - просто переходим на задачу
    TESTFLAG ftimer
    jz SchedGetPars ;если таймер был, обслуживаем его в первую очередь
    RESFLAG ftimer
    ld hl,Os_TIMERSENDER
    ld (Os_msg1+msg_sender),hl ;псевдосообщение, важен только sender
    jr SchedOk
    SchedGetPars
    call MsgGetPars
    SchedOk
    ld (Os_curapp),ix
    GETCONTEXT ;портит hl,нужен ix
    ret ;задача
    
    
    ;CY=адресат не найден
    SendMsg
    ;ix - ID адресата
    ;hl - тип сообщения
    ;bc,de,iy,bc',de',hl' - параметры
    ;Если при посылке сообщения очередь превысила лимит, то ставим текущую задачу в таймслайс и переход на задачу, которой шлём (если она не в таймслайсе)
    ;а если она в таймслайсе - крутимся, пока не будет готова, или до таймаута
    push hl
    STARTCRITICAL
    pop hl
    ;обязательно в критической секции!
    ld (Os_targetid),ix ;ID адресата
    ld (Os_msg2+msg_type),hl
    GETWORD HL,app_id
    ld (Os_msg2+msg_sender),hl
    ld (Os_msg2+msg_par0),bc
    ld (Os_msg2+msg_par1),de
    ld (Os_msg2+msg_par2),iy
    exx
    ld (Os_msg2+msg_par3),bc
    ld (Os_msg2+msg_par4),de
    ld (Os_msg2+msg_par5),hl
    ;exx
    RELEASECONTEXT ;портит hl,ix
    ;поиск адресата (если нету, то выход)
    ld bc,(Os_targetid) ;ID адресата
    ld ix,Os_Tapp-SIZEOF_app
    ld de,SIZEOF_app
    SendMsgSearch
    add ix,de
    ld a,(ix+app_priority)
    or a
    jz SendMsgError ;конец списка задач - не найдена нужная
    GETWORD HL,app_id
    ;CY=0
    sbc hl,bc
    jnz SendMsgSearch
    ;включаем главную страницу адресата
    GETWORD HL,app_mainpg
    SETPG
    SendMsgLoop
    ;есть место в очереди?
    GETWORD HL,app_curmsg
    GETWORD DE,app_endmsg
    or a
    sbc hl,de
    jz SendMsgNoRoom
    ;есть место {
    ;шлем сообщение
    ld hl,Os_msg2
    ld bc,15
    ldir
    ld a,(hl)
    ld (de),a
    inc e
    PUTWORD app_endmsg,DE
    ld ix,(Os_curapp)
    GETCONTEXT
    ENDCRITICAL
    or a ;CY=0
    ret ;отправитель
    SendMsgError
    ld ix,(Os_curapp)
    GETCONTEXT
    ENDCRITICAL
    scf ;CY=1
    ret ;отправитель
    ;}
    SendMsgNoRoom
    ;de=curmsg=endmsg
    ;нет места {
    TESTBIT ftimeslice ;адресат в таймслайсе?
    jnz SendMsgTimeSlice
    ;не в таймслайсе {
    ;берем первое сообщение
    push de
    exd
    ld de,Os_msg1
    ld bc,15
    ldir
    ld a,(hl)
    ld (de),a
    inc l
    PUTWORD app_curmsg,HL
    ;кладём наше сообщение
    pop de
    ld hl,Os_msg2
    ld bc,15
    ldir
    ld a,(hl)
    ld (de),a
    inc e
    PUTWORD app_endmsg,DE
    ;переход на адресат:
    ld (Os_curapp),ix
    ;ищем "следующую внутри приоритета", запоминаем ее
    GETWORD HL,app_next
    ld (Os_nextapp),hl
    ;exx
    ld bc,(Os_msg1+msg_par3)
    ld de,(Os_msg1+msg_par4)
    ld hl,(Os_msg1+msg_par5)
    exx
    ld bc,(Os_msg1+msg_par0)
    ld de,(Os_msg1+msg_par1)
    ld iy,(Os_msg1+msg_par2)
    GETCONTEXT ;портит hl,нужен ix
    ld hl,(Os_msg1+msg_type) ;обязательно в критической секции!
    ld ix,(Os_msg1+msg_sender) ;
    push hl
    ENDCRITICAL
    pop hl
    ret ;адресат
    ;}
    SendMsgTimeSlice
    ;в таймслайсе {
    ;берем контекст отправителя и входные регистры
    ;exx
    ld bc,(Os_msg2+msg_par3)
    ld de,(Os_msg2+msg_par4)
    ld hl,(Os_msg2+msg_par5)
    exx
    ld bc,(Os_msg2+msg_par0)
    ld de,(Os_msg2+msg_par1)
    ld iy,(Os_msg2+msg_par2)
    ld ix,(Os_curapp)
    GETCONTEXT ;портит hl,нужен ix
    ld hl,(Os_msg2+msg_type) ;обязательно в критической секции!
    ld ix,(Os_targetid) ;
    push hl
    ENDCRITICAL
    pop hl
    ;переключаем задачу
    ;желательно включить адресат
    ;иначе если адресат имеет приоритет ниже приоритета отправителя, то эта задача всегда будет включена снова раньше, чем хоть раз вызовется адресат - будет вечный цикл
    ;повиснет, если адресат сам ждет задачу, приоритет которой ниже, чем приоритет отправителя!!!
    ;поднять приоритет адресата до уровня отправителя?
    push af
    push hl
    push ...
    push ix
    pop bc ;ID адресата
    call CallTask ;переключиться на адресат (если не существует, то на любую задачу)
    ;выход из таймслайса
    pop ...
    pop hl
    pop af
    jp SendMsg ;новая попытка
    ;}
    ;}
    Взносы в призовой фонд конкурса "Твоя игра 5" принимаются с помощью PaуPal, ЯндексДенег или перевода на карту Сбербанка (см. http://ti5.retropc.ru)

  5. Этот пользователь поблагодарил alone за это полезное сообщение:
    Barmaley_m (04.07.2014)

  6. #3
    --- Аватар для Shadow Maker
    Регистрация
    01.03.2005
    Адрес
    Саранск
    Сообщений
    5,213
    Благодарностей: 869
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Помню еще такой вот проект: http://zx-pk.ru/showthread.php?t=21281
    Свирепый агрессивно-депрессивный мордовец!
    Не уверен - не напрягай!

    Не сдавайся. Дыши?

    Мордовия - Республика звука

  7. #4
    Vitamin C++ Аватар для Vitamin
    Регистрация
    14.01.2005
    Адрес
    Таганрог, Россия
    Сообщений
    4,031
    Благодарностей: 1426
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Barmaley_m Посмотреть сообщение
    В перспективе можно будет от нее отказаться, но для этого надо, чтобы устоялись структуры данных.
    Ну для этого можно
    а) использовать средства компилятора для организации структур (а не хардкодить оффсеты)
    б) использовать что-то типа assert'ов для контроля смещений (если их нет, надо добавить )
    Код:
    ;somewhere in defines
    STRUCT THREAD
    FLAGS BYTE
    INDEX BYTE
    ENDS
    
    ;somewhere in kernel
    ;hl- thread addr
    KiUnwaitThread:
            ASSERT(THREAD.FLAGS = 0)
            ASSERT(THREAD.INDEX = 1)
            ASSERT(THREAD = 2)
    	; Set the thread's state to Ready
    	set	THREAD_FLAG_READY,(hl)
    	; Check if the thread's priority is higher than of any other requesters
            inc     hl
    	ld	a,(request_thread)
    	cp	(hl)
    	ret	c
            ld     a,(hl)
    	ld	(request_thread),a			;the new thread has a higher priority
    	ld	(request_thread_ptr),hl
    	ret

  8. Эти 2 пользователя(ей) поблагодарили Vitamin за это полезное сообщение:
    alone (04.07.2014), Barmaley_m (04.07.2014)

  9. #5
    Veteran
    Регистрация
    06.05.2006
    Адрес
    Ливны, Орловская обл
    Сообщений
    1,169
    Благодарностей: 192
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Barmaley_m Посмотреть сообщение
    В диспетчере широко применяется индексная адресация, а она у Z80 жутко тормозная. В перспективе можно будет от нее отказаться
    Несмотря на тормоза с индексами, это на Z80 таки быстрейший способ _произвольного_ доступа к структурам на стеке, а писать с ними удобнее.

  10. #6
    Master
    Регистрация
    27.01.2005
    Сообщений
    525
    Благодарностей: 272
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Автор - молодец! Буду глядеть.

  11. #7
    Master
    Регистрация
    08.05.2007
    Адрес
    Dnepropetrovsk
    Сообщений
    880
    Благодарностей: 470
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от NovaStorm Посмотреть сообщение
    Несмотря на тормоза с индексами, это на Z80 таки быстрейший способ _произвольного_ доступа к структурам на стеке, а писать с ними удобнее.
    Поэтому и пишу пока с индексной. Впоследствии, когда структуры данных диспетчера устаканятся, можно будет поменять местами их члены и отказаться от произвольного доступа, а вместе с ним и индексной адресации.

  12. #8
    Activist Аватар для Alex/AT
    Регистрация
    14.03.2005
    Адрес
    Russia, Saint-Petersburg
    Сообщений
    213
    Благодарностей: 18
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Barmaley_m Посмотреть сообщение
    Здравствуйте, друзья.
    1) динамический приоритет потоков
    2) объекты ожидания типа Mutex
    3) пуск новых и завершение старых потоков
    На самом деле - не проще взять на развитие мой scheduler_g2? ( http://zx-pk.ru/showthread.php?t=21281 ) Там уже есть и кооперативная многозадачность, и вытесняющая, пуск потоков, и удаление, и блокировки, и синхронизация по тактам, и возможность вешать хуки сохранения/восстановления контекста (например страницу окна 3). Плюс он достаточно оптимизирован. И приоритет можно менять на ходу, если очень хочется (хотя нафиг это в рамках ZX нужно). Если хочется из него сделать полностью вытесняющий вариант без синхронизации - достаточно просто не вызывать функции отдачи таймслайса, и всё.
    Последний раз редактировалось Alex/AT; 13.07.2014 в 12:51.

  13. Этот пользователь поблагодарил Alex/AT за это полезное сообщение:
    Barmaley_m (15.07.2014)

  14. #9
    Master
    Регистрация
    24.05.2005
    Адрес
    г. Запорожье, Украина
    Сообщений
    680
    Благодарностей: 488
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Barmaley_m Посмотреть сообщение
    Описание его архитектуры и работы - на Хабре.
    Там в каментах что-то про файберы говорится - мол с ними совсем другой коленкор - есть инфа на русском про эти самые файберы ?

  15. #10
    Guru
    Регистрация
    03.01.2006
    Адрес
    Рязань
    Сообщений
    2,935
    Благодарностей: 1071
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Сйдя по википедии, файберы - это системная реализация сопроцедур. Что-то подобное было в моей статье тут: http://nedopc.org/nedopc/journal/NedoPC_5.pdf
    Более сложный вариант: http://pastebin.ru/L2KulLbB

    Вообще статьи о разработках такого плана приветствуются в нашем журнале.
    Взносы в призовой фонд конкурса "Твоя игра 5" принимаются с помощью PaуPal, ЯндексДенег или перевода на карту Сбербанка (см. http://ti5.retropc.ru)

  16. Этот пользователь поблагодарил alone за это полезное сообщение:
    shurik-ua (14.07.2014)

Страница 1 из 3 123 ПоследняяПоследняя

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Похожие темы

  1. Диспетчер памяти в KAY-1024...
    от SoftFelix в разделе Память
    Ответов: 16
    Последнее: 30.08.2010, 10:07
  2. диспетчер ROM памяти
    от p@lex в разделе Память
    Ответов: 5
    Последнее: 29.03.2010, 21:58
  3. Многозадачность
    от captain cobalt в разделе Оси
    Ответов: 23
    Последнее: 23.04.2005, 17:04

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •