- گروه شماره ۲۵
- معین آعلی - ۴۰۱۱۰۵۵۶۱
- ثمین اکبری - ۴۰۱۱۰۵۵۹۴
ابتدا با زدن دستور
man 2 pipe
صفحه راهنمای آن را مشاهده و مطالعه میکنیم:
حال میخواهیم یک
pipe
یکسویه ایجاد کنیم.
دستور pipe
دو
file descriptor
ایجاد میکند،
یکی از آنها برای خواندن و دیگری برای نوشتن مورد استفاده قرار خواهد گرفت.
اولی برای خواندن و دومی برای نوشتن.
با استفاده از کد زیر در پردازه اول یک پیام مینویسیم و در پردازه دوم با استفاده از
pipe
آن را میخوانیم:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd[2];
int res = pipe(fd);
if (res == -1) {
perror("pipe");
exit(1);
}
const char *msg = "Hello";
char buffer[100];
write(fd[1], msg, strlen(msg) + 1);
read(fd[0], buffer, sizeof(buffer));
printf("msg: %s\n", buffer);
return 0;
}نتیجه:
نحوهی کار کد بالا به این صورت است. هر چیزی که بر روی fd[1] نوشته شود، قابل خواندن با fd[0] خواهد بود.
حال اگر
fork
زده شود در این صورت پردازه فرزند هم
fd
های مربوط به پردازه پدر را دارد:
یک مشکل که داریم این است که در صورتی که هر دو پردازه بخواهند بر روی Pipe بنویسند و یا از آن بخوانند، به دلیل اینکه تنها یک بافر مشترک داریم، رفتار سیستم قابل پیش بینی نخواهد بود. در این حالت یک پردازه ممکن است دادهای که خودش بر روی Pipe قرار داده است را بخواند! بنابراین نیاز است که یک طرف تنها بر روی Pipe بنویسد و یک طرف تنها از آن بخواند. برای مثال فرض کنید پردازهی فرزند قصد خواندن از Pipe و پردازهی والد قصد نوشتن بر روی آن را دارد. به کمک فراخوانی سیستمی close ، پردازه والد fd[0] خود را میبندد (زیرا قصد خواندن ندارد) و پردازهی فرزند نیز fd[1] را خواهد بست.
با استفاده از کد زیر عملیات ذکرشدهی بالا را شبیهسازی میکنیم:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd[2];
pid_t pid;
if (pipe(fd) == -1) {
perror("pipe");
exit(1);
}
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid > 0) {
// mother
close(fd[0]);
const char *msg = "Hello World!";
write(fd[1], msg, strlen(msg) + 1);
close(fd[1]);
} else {
// child
close(fd[1]);
char buffer[100];
read(fd[0], buffer, sizeof(buffer));
printf("msg from father: %s\n", buffer);
close(fd[0]);
}
return 0;
}نتیجهی اجرا:
منطق کد بالا ساده است و در پاراگراف قبلی توضیح داده شده است، فقط باید توجه کنیم که پردازه فرزند دارای
pid==0
است.
حال قصد داریم با استفاده از pipe، fork، dup2 و exec ارتباطی بین دو پردازه برقرار کنیم که
پدر برنامه
ls
را اجرا کند و فرزند برنامهی
wc
را اجرا کند.
با این تفاوت که خروجی پدر به عنوان ورودی فرزند در نظر گرفته میشود:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
int fd[2];
pid_t pid;
if (pipe(fd) == -1) {
perror("pipe");
exit(1);
}
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) {
// child
close(fd[1]);
dup2(fd[0], 0);
close(fd[0]);
execlp("wc", "wc", NULL);
perror("execlp wc");
exit(1);
} else {
// father
close(fd[0]);
dup2(fd[1], 1);
close(fd[1]);
execlp("ls", "ls", NULL);
perror("execlp ls");
exit(1);
}
return 0;
}نتیجه:
استفاده از
pipe
تنها یک کانال یکطرفه میان دو پردازه به ما میدهد و نمیتوان به تنهایی با آن کانال دوطرفه ایجاد کرد.
اما میتوان برای ارتباط دو طرفه از دو
pipe
مختلف استفاده کنیم.
به این صورت که هر
pipe
فقط یک ارتباط یکطرفه ایجاد میکند.
البته باید توجه کنیم که حتما وقتی کار هر پردازه تمام شد از
close
استفاده کند. در غیر این صورت به
deadlock
میخوریم.
برای شبیهسازی این کانال دوطرفه کد زیر را پیادهسازی کردیم:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
int pipe1[2]; // child to father
int pipe2[2]; // father to child
if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {
perror("pipe");
exit(1);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) {
// child
close(pipe1[1]);
close(pipe2[0]);
char buffer[100];
read(pipe1[0], buffer, sizeof(buffer));
printf("from father received: %s\n", buffer);
const char *child_msg = "hello from child";
write(pipe2[1], child_msg, strlen(child_msg) + 1);
close(pipe1[0]);
close(pipe2[1]);
} else {
// father
close(pipe1[0]);
close(pipe2[1]);
const char *parent_msg = "hello from father";
write(pipe1[1], parent_msg, strlen(parent_msg) + 1);
char buffer[100];
read(pipe2[0], buffer, sizeof(buffer));
printf("from child received: %s\n", buffer);
close(pipe1[1]);
close(pipe2[0]);
}
return 0;
}نتیجهی اجرا:
با استفاده از دستور
man 7 signal
لیست سیگنالهای لینوکس را مشاهده میکنیم:
- SIGINT (Signal Interrupt)
این سیگنال زمانی به پردازه ارسال میشود که کاربر بهصورت دستی قصد توقف اجرای آن را دارد. این سیگنال معمولاً با فشردن کلید ترکیبی Ctrl+C در ترمینال فرستاده میشود. رفتار پیشفرض آن خاتمه دادن به پردازه است؛ اما برنامهها میتوانند با تعریف یک handler، این سیگنال را دریافت کرده و بهجای خاتمه، عملی دیگر انجام دهند
کد این سیگنال ۲ است.
- SIGHUP (Signal Hangup)
این سیگنال در ابتدا زمانی ارسال میشد که اتصال ترمینال با پردازه قطع میشد (مثلاً وقتی که کاربر ترمینال را میبست). رفتار پیشفرض آن خاتمه دادن به پردازه است. اما در سیستمهای مدرن، معمولاً از این سیگنال برای اطلاعرسانی به برنامهها برای بارگذاری مجدد تنظیمات استفاده میشود؛ بهویژه در سرویسهایی مانند وبسرورها یا daemonها. برنامهها میتوانند این سیگنال را مدیریت کرده و رفتار دلخواه خود را هنگام دریافت آن اجرا کنند. کد این سیگنال ۹ است.
- SIGSTOP (Stop)
این سیگنال برای متوقفکردن موقتی اجرای یک پردازه استفاده میشود. برخلاف دیگر سیگنالها، این سیگنال بهصورت غیرقابل کنترل است؛ یعنی برنامه دریافتکننده نمیتواند آن را مسدود کند، نادیده بگیرد یا برای آن handler بنویسد. پس از دریافت این سیگنال، پردازه بلافاصله متوقف میشود و اجرای آن تا دریافت سیگنال SIGCONT ادامه نخواهد یافت. این سیگنال معمولاً برای مدیریت پردازهها توسط سیستمعامل یا ابزارهای کنترلی استفاده میشود.
کد این سیگنال معمولا ۱۹ است.
- SIGCONT (Continue)
این سیگنال برای ادامهی اجرای پردازهای که قبلاً متوقف شده استفاده میشود. این سیگنال به پردازه میگوید که دوباره اجرا شود. برخلاف SIGSTOP، این سیگنال قابل کنترل و مدیریت است و برنامهها میتوانند در صورت تمایل، رفتاری خاص در زمان دریافت آن داشته باشند. معمولاً از این سیگنال در کنار SIGSTOP برای مدیریت اجرای پردازهها استفاده میشود.
کد این سیگنال ۱۸ است.
- SIGKILL (Kill)
این سیگنال برای پایان فوری و بیقیدوشرط یک پردازه استفاده میشود. پس از ارسال این سیگنال، پردازه فوراً توسط سیستمعامل خاتمه مییابد و هیچگونه فرصتی برای ذخیرهسازی، آزادسازی منابع یا واکنش به آن داده نمیشود. این سیگنال قابل مسدود کردن یا مدیریت توسط پردازه نیست و تنها راه مطمئن برای کشتن قطعی یک پردازه محسوب میشود. کد این سیگنال ۹ است.
سیگنال SIGALRM یک سیگنال زمانی است که از سوی تابع alarm به پردازه ارسال میشود تا پس از مدتزمان مشخصی (برحسب ثانیه) به آن اطلاع دهد.
این سیگنال یک تایمر راهاندازی میکند که پس از گذشت تعداد ثانیههای تعیینشده، سیگنال SIGALRM را به همان پردازه ارسال میکند.
رفتار پیشفرض SIGALRM خاتمه دادن به پردازه است.
این سیگنال اگر با ورودی ۰ صدا زده شود، تایمرهای قبلی را کنسل میکند و اگر با ورودی ۱ صدا زده شود، یک تایمر جدید تنظیم میکند. اگر از قبل تایمری فعال باشد آن را کنسل و این تایمر جدید را جایگزین میکند.
#include <stdio.h>
#include <unistd.h>
int main() {
alarm (5);
printf ("Looping forever . . . \n");
while (1);
printf("This line should never be executed\n");
return 0;
}کارکرد این برنامه بدین صورت است که ابتدا یک تایمر ۵ ثانیهای تنظیم میکند و سپس وارد یک لوپ بینهایت میشود. بعد از گذشت ۵ ثانیه سیگنال تایمر میرسد و چون برای این برنامه هیچ
handler
کاستومشدهای نوشته نشده، پس رفتار پیشفرض خود را انجام میدهد و پردازه پایان مییابد. در نتیجه هرگز خط اخر پرینت نمیشود.
خروجی:
حال میخواهیم کدی بنویسیم که پردازه را تا زمان دریافت سیگنال متوقف کند. برای این کار از
pause
و
sigaction
استفاده میکنیم:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void alarm_handler(int signum) {
printf("signal got!\n");
}
int main() {
struct sigaction sa;
sa.sa_handler = alarm_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
alarm(5);
printf("waiting ...\n");
pause();
printf("after SIGALRM\n");
return 0;
}تابع sigaction
رفتار پیشفرض سیگنال SIGALRM را با تابع دلخواه ما جایگزین میکند.
همچنین
sigemptyset(&sa.sa_mask)
برای این است که هنگام توقف سیگنالها را بلاک نکند.
نتیجهی اجرا:
در ادامه میخواهیم برنامهای بنویسیم تا کاربر با دو بار فشردن
CTRL + C
بتواند برنامه را متوقف کند.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int sig) {
printf("\nsignal got: %d\n", sig);
}
int main() {
signal(SIGINT, handler);
printf("start\n");
pause();
printf("enter CTRL+C again...\n");
pause();
printf("finished\n");
return 0;
}عملکرد برنامه:










