مگه پراسس حرفم میزنه؟

اینو باید با لحن هایده میخوندید . ( مگه معینم میخونه؟‌ )
خب سلام !
امروز میخوام راجع‌به یکی از مکانیزمای سیستم عامل صحبت کنم که خیلی جالبه و حتی ملموس !
دوستان این شما و این IPC !
Inter-Process Communication
ما از ارتباط خوشمون میاد ! همونطور که اگه هدفون تو گوشتون باشه علاقه مردم برای آدرس پرسیدن از شما صد‌ برابر میشه و احتمالا فکر میکنن کسی که تو گوشش چیزیه آدم مناسب تری برای پرسیدن آدرسه پراسس ها هم میل به برقراری رابطه باهم دارن !
ولی خب .. سیستم عامل با ارجاع به شریعت میگه که اگر دوتا پراسس باهم در یک فضای مموری باشن , نفر سوم حتما شیطانه ! پس ما فضای مموری مجزا برای پراسس‌ها داریم تا گناه شرعی شکل نگیره.
البته که میدونید هکر‌ها گاها سیستم عامل رو میپیچونن و با Buffer Overflow میرن تو خونه همدیگه ولی ما نمیخوایم سیستم عامل رو بپیچونیم بلکه با مجوز سیستم عامل میخوایم یک رابطه ی مشروع کاری بین دو پراسس برقرار کنیم.
به این منظور نیاز به یک فضا داریم ! یک سقف ...

تو فکر یک سقفم ! یه سقف بی روزن...

برای اینکه بتونیم دوتا پراسس رو بهم وصل کنیم یک فضای مشترک میخوایم که بتونن از طریقش باهم دیگه صحبت کنن. یعنی فلو رو اینجوری در نظر بگیرید.
دوتا پراسس در اتاق های جداگونه داریم و یک مکان از مموری سیستم عامل گرفتیم که بتونیم Share کنیم .
عکس ر ببینید :

یک فضای مشترک برای شما عزیزان ! ویلایی دو خواب
یک فضای مشترک برای شما عزیزان ! ویلایی دو خواب

حالا کار ساده تر شده ! در واقع اگه اینا بخوان باهم صحبت کنن سیستم عامل میتونه فضای Share شده روی فیزیکال مموری رو بهشون بده . دقیق تر بگیم هر پراسس روی Virtual Memory خودش یه مکانی رو میشناسه که اگه چیزی اونجا بنویسه اون یکی پراسس میتونه بخونتش.
ما میدونیم که اون آدرس Virtual Memory نگاشت میشه به Physical و اینکار رو تماما سیستم عامل انجام میده.

حالا این فضای مشترک رو چطور میتونیم از Windows بگیریم ؟ با استفاده از یک api به نام CreateFileMapping :

ورودی اولش یک هندله . هندل به چی ؟ ببینید این apiرو میشه دو جور استفاده کرد ! یا شما میخواید یک فایل رو از روی دیسک مپ کنید به مموری یا اینکه صرفا یک فضای مموری خالص نیاز دارید کاراتونو بکنید. اینجا طبیعتا نیاز ما دومیه و نمیخواد بهش هندل فایل خاصی رو بدیم.
پارامتر دومش مربوط به Security Attribute هست . در واقع یک استراکت 3 تایی هست که واقعا خودش یک دنیاییه. میتونید توش حق دسترسی به این آبجکت رو تعیین کنید. کی بتونه ببینتش؟ مکانیزمای کنترل دسترسیش چی باشن و اینا. چون نمیخوایم درگیر اینا باشیم و این بنده خدا هم یک پوینتر میخواد ما بهش NULL میدیم تا دیفالت تنظیماتش واسه مام اعمال بشه.
سومی هم بخش مهمیه که Protection روی پیج ها هست. میگه میخوای روش چه بلایی بیاری ؟ بخونی بنویسی ؟ مثلا میگیم Page_Read_Write . یعنی این دوتا حقو میخوام.
این دوتا Max, Min هم جریان جالبی دارن . میخواد بدونه حداکثر چقدر فایل میخوای مپ کنی رو مموری ؟ نکته جالبش اینه که این بنده خدا از زمان ۳۲ بیتی ها ورودی DWORD میگرفت. میدونیم که این دیتاتایپ یک عدد ۳۲ بیتیه پس نهایت حجمی که شما میتونید بهش بدید میشه ۴ گیگ :)) پس ویندوز واسه اینکه سازگاری بهم نریزه اومد دوتاش کرد Max , Min که مثل حد بالا و پایین میمونن و High رو فقط زمانی میدی که حجم فایل یا نیازت بیشتر از ۴ گیگ باشه.

اون lpName هم نام مشترکی هست که دوتا پراسس توافق میکنن باهم صحبت بکنن. در واقع این نام قرارداد بین این دوتا پراسس هست که بتونن دسترسی پیدا کنن به این بخش حافظه . پیشنهاد هم میشه که استرینگ سختی مثل یک guid باشه که یه پراسس دیگه هم اینو استفاده نکنه و کالیژن داشته باشه.
یه سوال بر عهده ی خواننده ! کیا میتونن این lpName رو ببینن؟ اگه بتونن ببینن معنیش اینه که رو حافظه مام میتونن چیزی بنویسن یا بخونن؟ هااااع..

مکان صحبت جور شد. حالا مونده یه اشاره ...یه اشااااره

در واقع اشاره گر . ( صرفا خواستم هدینگ خواننده ها خراب نشه )
حالا حافظه رو گرفتیم باید یک پوینتر ازش بگیریم تا بتونیم باهاش کار کنیم . اینکار رو با api به نام MapViewOfFile انجام میدیم.

به پارامتر اولش اون Handle که از api قبلی گرفتیم ر میدیم و دومی هم FILE_MAP_ALL_ACCESS و بقیه هم ۰ به جز ByteToMap که باید اندازه بافری باشه که تعیین کرده بودیم باهم صحبت کنیم.

یاروم میایه .. دلداروم میایه

حالا بریم سراغ سناریو خودمون ! یک پراسس داریم که مینویسه و دستور سیستمی میده اسمشو میذاریم Writer . یکی هم داریم که از حافظه میخونه و اجرا میکنه و جواب رو پرینت میکنه . اسمشو میذاریم Reader.
کد Writer رو این مدلی نوشتم :

void main(void) {

	const DWORD BUFFER_SIZE = 100;
	HANDLE MyMutex = CreateMutex(NULL, FALSE, _TEXT("MyMutex"));

	HANDLE file_mapping_handle = CreateFileMapping(INVALID_HANDLE_VALUE,
		NULL,
		PAGE_READWRITE,
		0,
		BUFFER_SIZE,
		_TEXT("Our_Buffer")
	);

	char* pointer_to_file = NULL;
	pointer_to_file = (char*)MapViewOfFile(file_mapping_handle, FILE_MAP_ALL_ACCESS, 0, 0, BUFFER_SIZE);


	BOOL is_running = TRUE;
	printf("Now You Can Request To another Process :) \n>");
	while (is_running) {

		WaitForSingleObject(MyMutex, INFINITE);
		char user_input[100] = { 0 };
		printf(">");
		fgets(user_input, 99, stdin);
		user_input[strcspn(user_input, "\n")] = '\0';
		sprintf_s(pointer_to_file, 100, "%s", user_input);
		ReleaseMutex(MyMutex);

	}

	UnmapViewOfFile(pointer_to_file);
	CloseHandle(file_mapping_handle);
	CloseHandle(MyMutex);

}

خیلی ساده ! یک آدرسی از مموری رو مپ میکنه . با همون MapView یک پوینتر کرکتری ازش میگیره . بعدش در یک حلقه بینهایت از یوزر دستور میگیره تا رانش کنه . بعد اینکه دریافت هم کرد دستور رو روی حافظه مشترک مینویسه تا یارش بخونه و اجراش کنه.
بخش WaitForSingleObject رو در ادامه تر میگم فعلا مهم نیست.

کد خواننده هم ساده تره . اگه ببینه چیزی نوشته شده دریافت میکنه و اجرا میکنتش :

#include <stdio.h>
#include <windows.h>
#include <tchar.h>



void main(void) {
	const DWORD BUFFER_SIZE = 100;
	HANDLE MyMutex = CreateMutex(NULL, FALSE, _TEXT("MyMutex"));


	HANDLE file_mapping_handle = CreateFileMapping(INVALID_HANDLE_VALUE,
		NULL,
		PAGE_READWRITE,
		0,
		BUFFER_SIZE,
		_TEXT("Our_Buffer")
	);

	char* pointer_to_file = NULL;
	pointer_to_file = (char*)MapViewOfFile(file_mapping_handle, FILE_MAP_ALL_ACCESS, 0, 0, BUFFER_SIZE);

	printf("Reader is Ready and Waiting for message \n");
	while (TRUE)
	{
		char recieved_message[100] = { 0 };
		WaitForSingleObject(MyMutex, INFINITE);
		sprintf_s(recieved_message, 100, "%s", pointer_to_file);
		printf("Received Message : %s\n", recieved_message);

		char buffer[1000] = { 0 };
		FILE* pipe = _popen(recieved_message, "r");
		char output_command[3000] = { 0 };
		while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
			strcat_s(output_command, sizeof(output_command), buffer);
		}
		_pclose(pipe);
		printf("%s", output_command);
		


		ReleaseMutex(MyMutex);

		


	}

	UnmapViewOfFile(pointer_to_file);
	CloseHandle(file_mapping_handle);
	CloseHandle(MyMutex);

}

که عشق آسان نمود اول ...

توی استفاده ی مشترک از منابع همیشه یک مشکل اساسی وجود داره به نام Race Condition یا مشکلات مربوط به همروندی ! یعنی چی؟
فرض کن دوتا پراسس ما دارن مینویسن و میخونن. چندتا سوال :
پراسس خواننده از کجا بفهمه که اون یارو یچیزی نوشته ؟ هی بیاد آدرس چک کنه؟
از کجا بدونه که چیزی که نوشته کامله و یا هنوز نوشتن یارو ادامه داره؟
اگه برفرض قرار بود یه چیزی هم بنویسه , از کجا معلوم روی یک مکان مشخص ننویسن و داده ی هم رو خراب نکنن !

تمام اینها سوالاتی هستن که اگه برنامه نویس حواسش بهشون نباشه جدای از ارور های منطقی , میرسه به آسیب پذیری TOCTOU . اخیرا هم بسیار پر سر و صدا شده آسیب پذیری های Time Of Check - Time Of Use .
پس پراسس ها باید یه جوری این مشکل رو حل میکردن .

تصویر یک انسان بی‌نظیر و خلاق :) جناب دایکسترا. بله، دقیقاً همون دایکسترای الگوریتم کوتاه‌ترین مسیره؛ فقط ظاهراً به یک حوزه قانع نبوده! کارهای درخشانی در سیستم عامل انجام داده مثل ایده ی سمافور و یا الگوریتم بانکدار و ...
تصویر یک انسان بی‌نظیر و خلاق :) جناب دایکسترا. بله، دقیقاً همون دایکسترای الگوریتم کوتاه‌ترین مسیره؛ فقط ظاهراً به یک حوزه قانع نبوده! کارهای درخشانی در سیستم عامل انجام داده مثل ایده ی سمافور و یا الگوریتم بانکدار و ...

یا منو ببر به خونتون یا بیا به خونه ی ما ...

این‌جا بود که لجندهایی مثل دایکسترا اومدن مفهوم mutual exclusion و semaphore رو پایه‌گذاری کردن — همون تئوری‌ای که بعدها سیستم‌عامل‌های مدرن مثل ویندوز روش سوار شدن و آبجکت‌های عملی مثل Mutex رو براساس همون ایده ساختن.
اومدن گفتن بیا یه چیزی تعریف کنیم تحت عنوان Critical Section . این محل باید فقط یک نفر توش باشه و اگر کسی در اون منطقه باشه , سیستم عامل اجازه نمیده کس دیگه ای بیاد تو !
این رو تحت عنوان یک آبجکت کرنلی به نام Mutex تعریف کردن .

گفتن ببین ! یک آبجکت میوتکس بسازین و رو اسمش باهم توافق کنید. اونجارو من براتون تک نفره میکنم !
این api که بالا میبینید بخش دومش ارث بری هست انگار که میگه صاحاب این میوتکس کیه؟ اگه TRUE بذارید اون پراسسی که ساخته مالکش میشه وخودش بدون نوبت میتونه بره تو منطقه بحرانی ! ولی اگه false بذارید آدما باید برای رفتن به Critical Section نوبت بگیرن و برن تو .
چه جوری نوبت بگیرن ؟ با WaitForSingleObject !

تو اولیه که هندل مربوط به میوتکس رو میدن و تو دومیه دارن میگن من چقدر حاضرم صبر کنم تا نوبتم بشه؟ شما میتونید برحسب میلی ثانیه بگید یا حتی INFINITE بذارید و بگید تا هر وقتی طول بکشه من میخوام برم تو . فرض کنید یکی بگیره کلا هم رها نکنه. اصلا کرش کنه . ما یه استادی داشتیم مثل میزد میگفت مثل اینه که یکی تو دستشویی غش کنه و بقیه دیگه نمیتونن برن تو Critical Section . پس چی میشه؟
آفرین . بد میشه.
اینجا بود که اومدن گفتن آقا . هر وقتی میرید تو Critical Section لطف کنید غش نکنید و api مربوطه بهش یعنی ReleaseMutex رو صدا کنید.

مکانیزم IPC در ویندوز و ارتباط پراسس ها با حافظه‌ی مشترک

تو کد ما چه اتفاقی میفته ؟ اگه دقت کنید وقتی نویسنده منتظره که ورودی از کاربر بگیره داخل Critical Section هست . پس خواننده ی بنده خدا اصلا نمیتونه وارد اون بخش بشه و روی WaitForSingleObject میمونه !
نویسنده ی ما کی از دستشویی میاد بیرون ؟ وقتی که یکبار محتوای کامند یوزر رو روی حافظه مشترک مینویسه میاد بیرون . پس بلافاصله بعدش خواننده میره داخل Critical Section .
اون کی میاد بیرون ؟ :)) وقتی که کل محتوای حافظه مشترک رو خوند و چاپ کرد میاد ReleaseMutexرو صدا میکنه و اون یکی میره داخل .
این مدلی پراسس های ما میتونن بدون مشکل همروندی یا Race باهم کار کنن.

آخر قصه بخوابیم ... اول ترانه پاشیم

تشکر از توجه شما . من در اینجا در یک حرکت ناگهانی و بدون داشتن پایان هدفمند یا با معنا متن رو تمام میکنم.

Windows via C/C++, Fifth Edition 
Windows Internals, Part 1 - Pavel Yosifovich
Eshareh- Haydeh
Saghf - Farhad