Оқулық «Федералдық білім беруді дамыту институты»


 Синхрондау. Оқиғалар, семафоралар



Pdf көрінісі
бет189/220
Дата25.02.2022
өлшемі2,22 Mb.
#26438
түріОқулық
1   ...   185   186   187   188   189   190   191   192   ...   220
Байланысты:
қазақша кітап

10.6.2. Синхрондау. Оқиғалар, семафоралар, 
мьютекстер
 
 
Бір  уақытта  жұмыс  істейтін  бірнеше  бәсекелес  процестерді 


244 
 
 
сүйемелдейтін  операциялық  жүйелердің  көпшілігі,  Windows  тобының 
операциялық  жүйелері  процестер  мен  ағымдарды  синхрондау 
механизмдерінің  бір  қатарын  құрайды.    Бұл  процестерге,  бөлінбейтін 
ресурстарды  бір  уақытта  қолданудан  құтылуға  мүмкіндік  береді,  бұл 
процестердің  бұзылуына,  кейбір  жағдайларда  оның  барлық 
жүйелерінің бүзылуына әкеледі.  
 
Windows  тобының  операциалық  жүйелері:  оқиғалар,  семафорлар, 
мьютекстер,  критикалық  облыстар  және  бәсекелесетін  процестердің 
арасында ресурстарды бөлуді ұйымдастыратын тәсілдердің бір қатары 
сияқты  синхрондау  механизмдерін  көтереді.  Берілген  тарауда  біз 
оқиғалар,  семафорлар  және  мьютекстер  сияқты  синхрондау 
механизмдерінің жұмыстарын қарастырамыз. 
 
Оқиға    (events)  синхрондау  механизмінің  обьектісі  ретінде 
ұсынылады,  кейбір  бағдарламалы  басқару  оқиғаларының  түскенін 
хабарлауға  арналған.    Синхрондаудің  екі  типі  бар    —  қолмен 
атқарылып тасталатын оқиғалар, және автоматты тасталатын оқиғалар. 
 
Автоматты  тасталатын  оқиғалар,  егер  осындай  оқиға  сигналды 
күйге  ауысқанда,  жүйе  автоматты  ағымның  сигналды  күйге  күтілуі 
таңдалады  және  оған  басқаруын  беруімен  ерекшеленеді.    Сонымен 
қатар, осындай оқиға сигналды емес күйге автоматты ауысады. Қолмен 
тасталатын  оқиғалармен  жұмыс  кезінде,  сигналды  емес  күйдегі 
оқиғаның қалпына келуі жауапкершілігін бағдарламалаушы алады. Бұл 
жағдайда ол, оқиғаны сигналды күйге ауыстыру қажет болғанда сайын 
ResetEvent()  функциясын нақты шақырады.
 
Оқиғаның құрылуы үшін  CreateEventQ функциясы қолданылады:
 
include 
 
HANDLE WINAPI CreateEvent(
 
LPSECURITY_ATTRIBUTES lpEventAttributes,
 
BOOL bManualReset,
 
BOOL bInitialState,
 
LPCTSTR lpName);
 
Бірінші    параметр  lpEventAttributes,  процесс-тобымен  құрылатын 
оқиғаның    дескрипторы  бар  немесе  мұраға  берілетін,  сондай-ақ 
құрылатын оқиғаның қолжеткізу құқығын өзгертуге жауап береді. Егер 
берілген    параметр  NULL  мәніне  ие  болса,  онда  дескриптор 
мұраланбайды  және  әдепкі  жағдай  бойынша  қол  жеткізу  құқығы 
қондырылады. 
 
Параметр  bManualReset,  оқиғаның  қандай  типінің  құрылуы  тиісті 
екеніне жауап береді. Егер осы параметр TRUE мәнін қабылдаса, онда 


245 
 
 
қолмен  тасталатын оқиғалар  құрылады.  Егер де  FALSE    мәні  берілсе, 
автоматикалық тасталатын оқиғалар құрылады. 
 
Параметр  bInitialState,  құрылатын  оқиғаның  бастапқы  күйін  беру 
үшін  арналған.    Егер  осы  параметр  TRUE  тең  болса,  онда  бастапқы 
сигналды  күйдегі  оқиғамен  оқиғалар  құрылады.  Қарсы  жағдайда, 
оқиғаның бастапқы күйі сигналды емеске қондырылады. 
 
LpName  параметр,  құрылатын  оқиғаның  атауын  беру  үшін 
қолданылады.  Егер  осы  параметр  NULL  тең  болса,  онда  анонимді 
объект-оқиғалар  құрылады.  Егер  осы  параметрде  аты  берілсе,  онда 
жүйе  осындай  аты  бар  оқиғаның  бар-жоғын  тексереді.  Егер  осындай 
оқиға  болмаса,  онда  бастапқы  қондырғылардың  талаптарымен  жаңа 
оқиға  құрылады.  Қарсы  жағдайда  жаңа  оқиға  құрылмайды,  бұдан 
бұрын  құрылған  берілген  атпен  ассоциацияланған  дескриптор 
ашылады.  Құрылатын  оқиғалар  саны  процесс  үшін    тек  жүйелік 
ресурстармен 
және 
дескрипторлардың 
шектелген 
санымен 
лимиттеледі, олар жүйе әкімшісімен өзгертіле алады. 
 
Жүйедегі  айқын  нұсқау  үшін  бұдан  бұрын  құрылған  оқиғаларға 
дескрипторды  алу  қажет,  ол  үшін  OpenEvent()  функциясы 
қолданылады:
 
include 
 
HANDLE WINAPI OpenEvent(
 
DWORD dwDesiredAccess,
 
BOOL bInheritHandle,
 
LPCTSTR lpName);
 
dwDesiredAccess  параметрі  жүйеге  пайдаланушының  оқиғаны 
басқару  үшін  қандай  рұқсат  құқықтарын  талап  ететінін  хабарлайды. 
Осы  параметр  оқиғаға  толық  рұқсатты  алуға  мүмкіндік  беретін 
EVENT_ALL_ACCESS  не  EVENT_MODIFY_  STATE  мәнін  өзгерте 
алады.  Бұл  мән  пайдаланушыға  оқиғаның  жай-күйін  өзгерте  алады 
және көп жағдайларда оның жұмыс істеуіне осы жеткілікті болады.
 
Параметр  bInheritHandle,  оқиғаның  құрылатын  дескрипторы 
еншілес  процеске  мұралана  ма,  жоқ  па,  соны  анықтайды.    Егер  осы 
параметр  TRUE  тең  болса,  онда  құрылатын  дескриптор  процесс-
тобымен  мұраланады,  басқа  жағдайда  мұраланбайтын  дескриптор 
құрылады.
 
Параметр lpName, бар оқиғаның атауын береді, пайдаланушы оған 
қолжеткізу  құқығына  жеткісі  келеді.  Осылайша,  осы  функцияның 
көмегімен  тек  атаулы  оқиғаларға  қол  жеткізуге  болады.  Анонимді 
оқиғалар,  оқиға  құратын  процестерді  және  ағымдарды  қолдануы 


246 
 
 
мүмкін.  
 
Кез  келген  тип  оқиғасының  сигналды  күйге  ауысуы  SetEvent() 
функциясымен жүзеге асырылады.:
 
include 
 
DWORD WINAPI SetEvent(HANDLE hEvent);
 
Параметрі  ретінде  осы  функцияға,  сол  оқиғаның  дескрипторы 
беріледі, ол сигналды күйге ауысуы керек.  
 
Оқиғаның тасталуы үшін ResetEvent() функциясы қолданылады: 
include 
 
DWORD WINAPI ResetEvent(HANDLE hEvent);
 
Параметр hEvent, өз кезегінде, күйі сигналды жағдайдан, сигналдық 
емес қалпына келетін оқиғаның дескрипторын береді. 
 
Процестер  немесе  ағымдар  жұмысының  аяқталуын  күткен 
жағдайда,  процесс  басқа  оқиғаның  сигналды  күйге  ауысуы,  жоғарыда 
қарастырылған  WaitForSingleObjectQ  және  WaitForMultipleObjectsQ 
функцияларының көмегімен жүретінін хабарлайды.
 
Процестер  мен  ағымдардың  синхрондау  үшін  оқиғалардың 
қолданылуына  мысал  ретінде,  жоғарыдағы,  әртүрлі  уақыт  орындалуы 
бар, екі процестің үйлестірілген мысалын қарастырамыз.:
 
#include 
 
#include 
 
#include 
 
DWORD WINAPI ThreadProc(LPVOID lpParam); 
void ErrorReport(LPTSTRlpszFunction);  
int main()
 
{
 
DWORD dwThreadId;
 
HANDLE hThread, hEvent1, hEvent2; 
unsigned i;
 
// автоматты тасталатын оқиға құрамыз // 
бастапқы сигналды емес оқиғамен 
if((hEvent1=CreateEvent(NULL, FALSE, FALSE, 
"Thread1")) == NULL)
 
{
 
ErrorReport(TEXT("CreateEvent()")); 
return(1);
 
}
 
// бірінші ағыммен синхрондауға тырысатын 


247 
 
 
ағым құрамыз // hThread = CreateThread(
 
NULL, 
//  әдепкі бойынша құқық
 
0, 
//  әдеттегідей қамшы өлшемі
 
ThreadProc, // ағымның функциясы 
 
NULL, 
//  функция үшін аргумент
 
0, 
//  әдеттегідей жалауы жоқ
 
&dwThreadId); if 
(hThread == NULL)
 
{
 
ErrorReport(TEXT("CreateThread()")); 
return(1);
 
}
 
// екінші ағынмен тағы бір оқиғаның құрылуын күтеміз 
 
// екінші оқиғаның құрылғанынан кейін//  ол алғашқы 
оқиғаны сигналды күйге ауыстырады // ResetEvents() 
функциясы шақырылмайды,
 
// себебі оқиға автоматты тасталынады 
WaitForSingleObject(hEvent1, INFINITE);
 
// туындаған ағыммен құрылған оқиғаны ашамыз 
if((hEvent2 = OpenEvent(EVENT_ALL_ACCESS, FALSE, 
"Thread2")) == NULL)
 
{
 
ErrorReport(TEXT("OpenEvent()")); 
return(1);
 
}
 
// ағым-ұрпағына басқаруды береміз // екінші оқиғаны 
сигналды күйге SetEvent(hEvent2);
 
// алғашқы ағымның негізгі цикл for(i = 0; i < 10; 
++i)
 
{
 
// туындаған ағымның басқарылуын беруді күтеміз // 
 
WaitForSingleObject(hEvent1, INFINITE);
 
// ақпараттарды консолға шығарамыз printf("p%d ", 
i);
 
// алғашқы ағымды 1 сек блоктаймыз Sleep(1000);
 
// туындаған ағымға басқаруды береміз 
SetEvent(hEvent2);
 
}
 
// туындаған ағымның аяқталуын күтеміз 
WaitForSingleObject(hThread, INFINITE);
 
// оның  дескрипторын жабамыз CloseHandle(hThread);
 
// оқиғаның дескрипторын жабамыз 
CloseHandle(hEvent1);
 
CloseHandle(hEvent2);
 
printf("\n");
 


248 
 
 
return(0);
 
}
 
DWORD WINAPI ThreadProc(LPVOID lpParam)
 
{
 
HANDLE hEvent1, hEvent2;
 
unsigned i;
 
// алғашқы ағыммен құрылған оқиғаны ашамыз if((hEvent1 = 
OpenEvent(EVENT_ALL_ACCESS, FALSE, "Threadl")) == NULL)
 
{
 
ErrorReport(TEXT("OpenEvent()")); 
return(1);
 
}
 
// автоматты лақтыруды құрамыз С // сигналды емес 
алғашқы күймен ((hEvent2 =CreateEvent(NULL, FALSE, 
FALSE, "Thread2")) == NULL)
 
{
 
ErrorReport(TEXT("CreateEvent()")); 
return(1);
 
}
 
// алғашқы ағымға, қазіргі ағымда құрылған 
SetEvent(hEvent1) оқиғаны ашу үшін // береміз 
басқаруды;
 
// еншілес ағымның негізгі циклі for(i = 0; i < 10; 
++i)
 
{
 
// алғашқы ағымнан басқару жіберілімдерін алу 
// 
 
WaitForSingleObject(hEvent2, INFINITE);
 
// ақпараттарды консолға шығарамыз printf("c%d 
", i);
 
// ағымды 4 сек блоктаймыз Sleep(4000);
 
// алғашқы ағымның басқаруын жіберу 
SetEvent(hEvent1);
 
}
 
// оқиғаның дескрипторын жабамыз  
CloseHandle(hEvent1);
 
CloseHandle(hEvent2);
 
return(0);
 
}
 
void ErrorReport(LPTSTR lpszFunction)
 
{
 
LPVOIDlpMsgBuf;
 


249 
 
 
LPVOID lpDisplayBuf;
 
DWORD dw = GetLastError();
 
FormatMessage(
 
FORMAT_MESSAGE_ALLOCATE_BUFFER | 
FORMAT_MESSAGE_FROM_SYSTEM,
 
NULL,
 
dw,
 
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) 
&lpMsgBuf,
 
0, NULL ) ;
 
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
(lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR) 
lpszFunction)+40)*sizeof(TCHAR)); 
_stprintf((LPTSTR)lpDisplayBuf,
 
TEXT("%s failed with error %d: %s"), lpszFunction, 
dw, lpMsgBuf);
 
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, 
TEXT("Error"), MB_OK);
 
LocalFree(lpMsgBuf);
 
LocalFree(lpDisplayBuf); 
}
 
 
Осы бағдарламаның жұмысы нәтижесінде консолға келесі түрдің 
жолы шығады: 
 
c0 p0 c1 p1 c2 p2 c3 p3 c4 p4 c5 p5 c6 p6 c7 p7 c8 p8 c9 
p9
 
Ағымдарды блоктаудың әр түрлі уақыттарына қарамастан, олардың 
шығарылуы  қатаң  кезектеседі.  Егер  де  жоғарыда  келтірілген 
бағдарламаларда циклдің ішінде SetEvent() және WaitForSmgleObjectQ 
функцияларының шақыртуларын және алғашқы ағым мен ағым-тобын 
түсіндіре кетсе, онда бағдарлама жұмысының нәтижесі келесі түрге ие 
болады: 
 
p0 c0 p1 p2 p3 p4 c1 p5 p6 p7 p8 c2 p9 c3 c4 c5 c6 c7 c8 
c9
 
бірінші  ағымның  ағым-тобын  еш  синхрондамағаны  көрініп  тұр 
және  еншілес  ағымға  қарағанда,  өзінің  негізгі  жұмысын  ертерек 
аяқтайды. 
 
Екінші механизм Windowsті синхрондау үшін қолданылады,  Linux 


250 
 
 
— семафор синхрондағыш құрылғылармен танысқанда кездестіргенбіз. 
Windows семафорлары ішкі есептегіші бар синхрондаушы обьектілерін 
ұсынады,  оның  мәні  нөлден  берілген  максималды  мәнге  дейінгі 
диапозонда орналасуы мүмкін. Бұл есептегіштің мәні, ағымның біріне 
(WaitForSingleObjectQ и WaitForMultipleObjects()) күту функциясының 
басқарылуы  қайтарылған  сайын  азаяды,  ол  ағым  семафорды 
босатқанда  артады.  Семафор,  есептегіштің  мәні  0-ге  тең  болмағанда 
сигналды күйге түседі, ал есептегіш мәні 0-ге тең болғанда, сигналдық 
емес болады. 
 
Семафорды құру үшін CreateSemaphore() функциясы қолданылады: 
 
#include 
 
HANDLE WINAPI CreateSemaphore(
 
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
 
LONG lInitialCount,
 
LONG lMaximumCount,
 
LPCTSTR lpName);
 
LpSemaphoreAttributes  параметрі  процесс  тобының  құрылатын 
семафор    дескрипторын  мұраланатынын  не  мұраланбайтынына  жауап 
береді,  сондай-ақ  оның  қолжеткізу  құқығын  өзгертеді.  Егер  осы 
параметр  NULL  мәніне  ие  болса,  дескриптор  топпен  мұраланбайды 
және әдеттегідей қолжеткізу құқығын орнатады. 
 
LInitialCount 
параметрінде  семафордың  ішкі  есептегішінің 
бастапқы мәндері беріледі.  Есептегіштің максималды қолжетімді мәні 
lMaximumCount параметрінде беріледі.
 
Параметр  lpName,  құрылатын  семафордың  атауын  беруге 
қолданылады. Егер  осы  параметр NULL  тең  болса,  анонимді семафор 
құрылады.  Егер  де  Windows  жүйелік  обьектілерін  құру  кезінде 
қолданылған атауы берілетін болса, оқиғалар, семафорлар, мьютекстер, 
онда семафор құрылмайды және функция қате аяқталады. 
 
Егер  функция  сәтті  аяқталса  және  семафор  құрылса,  онда 
CreateSemaphore()  функциясы  құрылған  семафордың  дескрипторын 
қайтарады. Басқа жағдайда функция NULL мәнін қайтарады. Құрылған 
семафордың саны, оқиғалар саны сияқты, жүйелік ресурспен және бір 
ғана процесті құратын, дескриптор саны лимитімен шектеледі.
 
Алдымен  құрылған  семафордың  дескрипторын  алу  қажеттілігін 
жүйеде көрсету үшін OpenSemaphore() функция қолданылады:
 
include 
 
HANDLE WINAPI OpenSemaphore(
 
DWORD dwDesiredAccess,
 
BOOL bInheritHandle,
 


251 
 
 
LPCTSTR lpName);
 
Параметр dwDesiredAccess, семафорды басқару үшін пайдаланушы 
қандай  қол  жеткізу  құқықтарын  талап  етіп,  жүйеге  хабарлайды.  Бұл 
параметр 
обьектіге 
толық 
қолжеткізуге 
мүмкіндік 
беретін 
SEMAPHORE_ALL_ACCESS немесе SEMAPHORE_ MODIFY_STATE 
мәнін қабылдайды. 
 
 
Параметр  bInheritHandle,  семафордың  құрылатын  дескрипторы, 
еншілес  процесті  мұрағаттайтынын  анықтайды.    Егер  осы    параметр 
TRUE  мәніне  ие  болса,  онда  құрылатын  дескриптор  процесс-тобымен 
мұрағатталады керісінше мұрағаттанбайтын дескриптор құрылады.
 
Параметр  lpName  қолжетімділікті  алуы  қажет,  алдыңғы  құрылған  
семафордың  атауын  береді.  Осы  функцияның  көмегімен  атаулы 
семафорға  ғана  қол  жеткізуге  болады,  ал  атаусыз  семафорлар  осы 
обьекттің  және ағымдары құратын процесспен ғана қолданылады.  
 
Семафорды 
босату 
үшін 
ReleaseSemaphore() 
функциясы 
қолданылады:
 
include 
 
BOOL WINAPI ReleaseSemaphore(
 
HANDLE hSemaphore,
 
LONG lReleaseCount,
 
LPLONG lpPreviousCount);
 
Параметр  hSemaphore,  босатылуы  тиіс  семафордың  дескрипторын 
береді.  Параметр  lReleaseCount,  семафордың  ішкі  есептегішін 
арттыратын,  мәнді  береді.    Берілген  мән    0-ден  көп  болуы  қажет. 
Параметр  lpPreviousCount,  семафор  есептегішінің  алдыңғы  мәндерін 
қайтару үшін қолданылады.
 
Windows  –  те  процесстер  мен  ағымдарды  синхрондаудың  бірі 
мьютекс болып табылады. Мьютекс  өзімен, бір ағымға да жатпайтын 
болғанда,  сигналды  күйге  ауысатын,  синхрондау  обьектісін  ұсынады. 
Егер  де  мьютекс қандай  да  бір ағымға  жататын болса,  онда  сигналды 
емес  күйге  ауысады.    Осы  тұрғыда,  мьютекстер,  олармен  бөлінетін 
ресурсқа  бірнеше  ағымның  қол  жеткізуін  болдырмауды  ұйымдастыру 
кезінде,  ыңғайлы.    Мұндай  ресурсқа  мысал  болып,  бөлінетін  жады 
сияқты, процессаралық өзара әрекеттесудің объектісі болып табылады. 
 
Осы  объектіге  ақпараттарды  жазуға,  әр  уақыт  сәтінде  бір  есептеуші 
ағым міндетті. Сондықтан, мұндай процессаралық өзара әрекеттесудің 
механизмінің  қолжетімділігін болдырмауды ұйымдастырудың міндеті 
өте  маңызды  болып  табылады.  Жалпы  мьютекстерді  семафордың  бір 


252 
 
 
нұсқасы ретінде қарастыруға болады.  
 
Мьютексті құру үшін  CreateMutexQ функциясы қолданылады:
 
#include 
 
HANDLE WINAPI CreateMutex(
 
LPSECURITY_ATTRIBUTES lpMutexAttributes,
 
BOOL bInitialOwner,
 
LPCTSTR lpName);
 
 
Параметр  lpMutexAttributes  топтарымен  құрылатын    мьютекс 
дескрипторы  мұралануға,  сондай-ақ  оның  қолжеткізу  құқығын 
өзгертілуіне  жауап  береді.  Егер  осы  параметр  NULL  мәніне  ие  болса, 
дескриптор мұраланбайды және әдеттегідей құқықтары қондырылады.  
Егер  bInitialOwner  параметрі    TRUE  тең  болса,  онда    мьютекс 
құрған  ағым,  пайдаланушымен  жарияланады.  Егер  де  осы    параметр 
FALSE  тең  болса,  онда  мьютексті  құратын  есептеуші  ағым,  оны 
иелікке алмайды.  
LpName  параметрі,  мьютекстің  атауын  беру  үшін  қолданылады. 
Егер  осы параметр NULL тең болса анонимді объект құрылады. Егер 
Windows  –  те  оқиға,  семафор,  мьютекс  т.б.  сияқты  жүйелік 
обьектілерін сәтсіз құруда, функция қатемен аяқталады. Егер функция 
сәтті  аяқталса  және  семафор  құрылса  CreateMutexQ  функциясы 
құрылған    объектінің  дескрипторын  қайтарады.  Басқа  жағдайда 
функция NULL мәнін қайтарады.
 
Жүйеде  құрылған  мьютекстердің  максималды  саны,  жүйедегі  бос 
ресурстардың  болуымен  (бос  виртуалды  жадының  ерекшелігі)  және 
процестегі дескриптор санының шектелуімен анықталады. 
 
Алдымен  құрылған  мьютекстің  дескрипторын  алу  қажеттілігін, 
жүйеде нақты көрсету үшін  OpenMutex() функциясы қолданылады:
 
include 
 
HANDLE WINAPI OpenMutex(
 
DWORD dwDesiredAccess,
 
BOOL bInheritHandle,
 
LPCTSTR lpName); 
 
DwDesiredAccess  параметрі  пайдаланушының  мьютексті  басқару 
үшін  қандай  қолжетімділік  құқықтарын  талап  ететінін  жүйеге 
хабарлайды.  Осы  параметр  обьектіге  толық  қол  жеткізу  үшін 
MUTEX_ALL_ACCESS  немесе  MUTEX_  MODIFY_STATE  мәндерін 
қабылдай алады.
 


253 
 
 
ВInheritHandle  параметрі,  мьютекстің  құрылатын  дескрипторы 
еншілес  процеске  мұрағатталатынын  анықтайды.    Егер  бұл    параметр 
TRUE  мәніне  ие  болса,  құрылатын  дескриптор  еншілес  процесті 
мұралайды, басқа жағдайда  мұраланбайтын дескриптор құрылады.
 
Параметр  lpName  қолжетімділікті  алуы  керек    мьютекстің  атауын 
береді.    Осы  функцияның  көмегімен,  тек  атаулы    семафорға 
қолжетімділік  алуға  болады,  ал  атаусыз  семафорлар,  тек  берілген 
обьект және оның тобы құратын процеспен ғана пайдаланылады. 
 
Мьютексті босату үшін ReleaseMutex() функциясы қолданылады: 
 
include 
 
BOOL WINAPI ReleaseMutex(HANDLE hMutex);
 
Параметр  hSemaphore  босатылуы  қажет  мьютекстің  дескрипторын 
береді.  
 
Есептеуші  ағымдарды  синхрондау  үшін  мьютекстер  мен 
семафорларды  пайдалану  үлгісін  қарастырамыз.  Ол  үшін  алдында 
қарастырылған  үлгіні  жетілдіреміз  және  синхрондаудың  екі 
механизмін қолданамыз: семафорлар және мьютекстер.
 


Достарыңызбен бөлісу:
1   ...   185   186   187   188   189   190   191   192   ...   220




©emirsaba.org 2024
әкімшілігінің қараңыз

    Басты бет