برنامه نویسی سیستمی ،  IPC-1 (Unnamed PIPE)

مقدمهسلام ، در اولین سری از نوشته هایم در اینجا قصد دارم به شرح IPC (Inter Process Communication) در سیستم عامل Windows بپردازم ، در هر متن از سلسله متونی که قصد نگارش دارم، به بررسی یکی از شیوه های آن خواهم پرداخت ، رویکرد این متن ها یادگیری برنامه نویسی سیستمی (Windows System Programming) میباشد و مطالب آن عمدتا از داکیومنت های msdn ، کتاب Windows 10 System Programming و Windows Via Cpp تهیه و جمع آوری شده است.معرفی : Inter-Process Communication در حقیقت به معنای مکانیزم هایی است که پردازه های گوناگون برای تبادل پیام با یکدیگر به کار میبرند، روش های مختلفی برای اینکار وجود دارد مانند : PIPE, MailSlot, Filemapping و ...برخی از این شیوه ها برای پردازه های موجود در سیستم پایانی (End-System) مشترک و برخی به منظور مبادله پیام پردازه هایی در سیستم های پایانی غیر یکسان هستند ، که در ادامه به مرور با آن ها آشنا میشویم.روش اول : Unnamed PIPE معرفی : این روش به منظور ارسال پیام بین پردازه های با رابطه پدر-فرزندی (Child-Parent) که به طور واضح در یک سیستم پایانی مشترک هستند به کار میرود و نکته مهم در این شیوه این است که ارتباط برقرار شده یکطرفه می باشد و یک پردازه عملیات Write و پردازه دیگر عملیات Read را انجام میدهد ، تصویر زیر که در کتاب Windows 10 System Programming آمده است میتواند در درک این موضوع به ما کمک کند : برای اینکه به یاد آورید در کجا این IPC را مشاهده کرده اید دستور زیر را مشاهده کنید :C:\>dir | findstr &quotUser&quot 03/02/2023 10:04 AM <DIR> Usersمثال بالا ، مثالی از Anonymous Pip است ، که مشاهده میکنید دستور dir ابتدا اسم فایل های موجود در یک مسیر را لیست میکند سپس آن ها را به | که یک Anonymous PIPEاست ، ارسال میکند ، تا خروجی dir به عنوان ورودی به findstr داده شود و به دنبال فایل هایی که در نام آن ها "User" هست بگردد.کد نویسی : ساخت PIPE با استفاده از تابع سیستمی CreatePipe انجام میشود که نگارش آن را میتواند مشاهده کنید : c++ BOOL CreatePipe( [out] PHANDLE hReadPipe, [out] PHANDLE hWritePipe, [in, optional] LPSECURITY_ATTRIBUTES lpPipeAttributes, [in] DWORD nSize );اگر به یاد داشته باشید ، ذکر کردیم PIPE یک ارتباط یکسویه را برای ما ایجاد میکند ، که یک پردازه عملیات Write و دیگری عملیات Read را انجام میدهد فلذا این تابع سیستمی دو HANDLE را به ما برمیگرداند که یکی از آن ها به منظور استفاده پردازه نویسنده و دیگری به منظور استفاده پردازه خواننده است .نکته ای که باید توجه داشته باشید این است hReadPipe دسترسی readonly و hWritePipe دسترسی writeonly به Pipe دارند.حال سوالی که مطرح میشود این است که عملیات های خواندن و نوشتن از PIPE توسط کدام توابع سیستمی انجام میشود ؟ پاسخ این سوال بسیار ساده است ، این عملیات ها با استفاده از توابع سیستمی ReadFile و WriteFile انجام میشود.تصویر فوق یک Object مشترک بین دو پردازه را با Handle های یکسان 0xa8 نشان میدهد که نامی هم ندارد و این Object در حقیقت همان PIPE است که ما بین این دو پردازه ایجاد کرده ایم .سوال بعدی که ایجاد میشود این است ، که پردازه فرزند ، چطور میتواند این object را در اختیار داشته باشد ، در حقیقت همانطور که در ادامه در کد مشاهده میکنید ، PIPE توسط پردازه پدر ساخته میشود و با پردازه فرزند به اشتراک گذاشته میشود اما چگونه این به اشتراک گذاری انجام میشود ؟یکی از روش های به اشتراک گذاری Kernel Object ها ارث بری فرزند از پدر است ، در این سناریو ، یک یا چند kernel object در پردازه پدر موجود است ، که تصمیم میگیرد ، پردازه فرزند به تعداد مشخصی از آن ها دسترسی داشته باشد . برای این به اصطلاح ارث بری Parent Process باید گام هایی بردارد:زمانی که پردازه پدر میخواهد یک kernel object بسازد باید به سیستم بفهماند handle این object قابل ارث بری است ( در اینجا Object Inheritance نداریم بلکه Handle Inheritance وجود دارد) ، در SecurityAttribute ها ما فیلدی به نام bInheritHandle داریم که نوع آن Boolean میباشد این فیلد دقیقا همان جایی است که پردازه پدر به سیستم میفهماند که handle ساخته شده برای این kernel obeject قابل ارث بری است و در صورت NULL قرار دادن آن این ارث بری اتفاق نمیافتد: SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; // Make the returned handle inheritable. HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);زمانی که شما در پردازه پدر با استفاده از تابع سیستمی CreateProcess یک پردازه فرزند ایجاد میکنید اگر به آرگمان bInheritHandles آن مقدار False دهید به سیستم میگویید که نمیخواهید فرزند ، عملیات ارث بری Handle ها را انجام دهد اما زمانی که به این آرگمان مقدار True را پاس میدهید ، در حقیقت قصد دارید Inheritable Handle های پدر را در فرزند ارثبری کنید ، در این حالت پس از ایجاد پردازه فرزند ، اجازه اجرا به آن داده نمیشود ، و در ابتدا برای پردازه فرزند نیز یک Handle Table جدید ساخته میشود اما یک گام اضافی در این حالت نیز وجود دارد آن هم اینکه به سراغ Parent Handle table میرود و هر kernel object را که Inheritable هست در Child Handle Table کپی میکند.در حقیقت در این حالت اگر پردازه A ، پردازه B را بسازد Inheritable handle های A با B به اشتراک گذاشته میشود و مشاهده میکنید که مقادریرشان یکسان میباشد ،جدول های زیر را با دقت مشاهده کنید ، این جدول ها از کتاب Windows Via Cpp اورده شده است : در جدول فوق پردازه پدر دارای دو handle به kernel object های مختلفی است که شماره 1 به اشتراک با پردازه فرزند گذاشته نشده است اما در شماره 3 این اشتراک گذاری اتفاق افتاده است حال در child داریم :اما نکته مهم این است که پردازه فرزند نمیداند کدام یک از Handle های جدول خود را به ارث برده است ! و ما این را با استفاده از Standard Output ها به آن میفهمانیم :حال به کدهایی که برای پردازه های پدر و فرزند توسعه داده شده است دقت کنید : پردازه پدر : #include <windows.h> #include <iostream> using namespace std; int main() { HANDLE hReadPipe, hWritePipe; SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; // create pipe if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) { cerr << &quotCreatePipe failed&quot << endl; return 1; } STARTUPINFO si = { 0 }; si.cb = sizeof(si); si.hStdOutput = hWritePipe; // redirect child process's stdout to the write end of the pipe si.dwFlags |= STARTF_USESTDHANDLES; PROCESS_INFORMATION pi = { 0 }; // create child process&quotD:\Windows_Via_CC\SelfCodes\IPC\IPCUsingUnnamedPipe\childProcess\x64\Debug\childProcess.exe&quot if (!CreateProcess(NULL, (LPSTR)&quotD:\\Windows_Via_CC\\SelfCodes\\IPC\\IPCUsingUnnamedPipe\\childProcess\\x64\\Debug\\childProcess.exe&quot, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { cerr << &quotCreateProcess failed&quot << endl; return 1; } // close the write end of the pipe in the parent process CloseHandle(hWritePipe); // read message from the read end of the pipe char buffer[1024]; DWORD bytesRead = 0; while (ReadFile(hReadPipe, buffer, 1024, &bytesRead, NULL) && bytesRead > 0) { std::cout.write(buffer, bytesRead); } // close the read end of the pipe in the parent process CloseHandle(hReadPipe); return 0; } پردازه فرزند : #include <windows.h> #include <iostream> using namespace std; int main() { // get the handle of the standard output HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); // write message to the standard output const char* message = &quotHello from child process!&quot DWORD bytesWritten = 0; if (!WriteFile(hStdout, message, strlen(message), &bytesWritten, NULL)) { cerr << &quotWriteFile failed&quot << endl; return 1; } return 0; } خروجی اجرای برنامه پدر : Hello from child process!