5.6 צינורות פתרון
פתרונות - צינורות - pipes¶
פתרון 1 - אב שולח הודעה לבן¶
כתבו תוכנית שיוצרת צינור, קוראת ל-fork, האב שולח "hello child!" דרך הצינור, והבן קורא ומדפיס.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main() {
int pipefd[2];
pipe(pipefd);
pid_t pid = fork();
if (pid == 0) {
// תהליך הבן - קורא מהצינור
close(pipefd[1]); // סוגרים את צד הכתיבה
char buf[128];
int n = read(pipefd[0], buf, sizeof(buf) - 1);
buf[n] = '\0';
printf("child received: %s\n", buf);
close(pipefd[0]);
_exit(0);
} else {
// תהליך האב - כותב לצינור
close(pipefd[0]); // סוגרים את צד הקריאה
const char *msg = "hello child!";
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]);
wait(NULL);
}
return 0;
}
פתרון 2 - בן שולח PID לאב¶
כתבו תוכנית שיוצרת צינור, קוראת ל-fork, הבן שולח את הPID שלו כמחרוזת דרך הצינור, והאב קורא ומדפיס.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main() {
int pipefd[2];
pipe(pipefd);
pid_t pid = fork();
if (pid == 0) {
// תהליך הבן - כותב את הPID שלו לצינור
close(pipefd[0]); // סוגרים את צד הקריאה
char buf[64];
sprintf(buf, "child PID is: %d", getpid());
write(pipefd[1], buf, strlen(buf));
close(pipefd[1]);
_exit(0);
} else {
// תהליך האב - קורא מהצינור
close(pipefd[1]); // סוגרים את צד הכתיבה
char buf[128];
int n = read(pipefd[0], buf, sizeof(buf) - 1);
buf[n] = '\0';
printf("parent received: %s\n", buf);
close(pipefd[0]);
wait(NULL);
}
return 0;
}
פתרון 3 - מימוש ls | wc -l¶
ממשו תוכנית שמבצעת את מה ש-ls | wc -l עושה.
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int pipefd[2];
pipe(pipefd);
// תהליך בן ראשון - מריץ ls
pid_t pid1 = fork();
if (pid1 == 0) {
dup2(pipefd[1], 1); // stdout -> צד הכתיבה של הצינור
close(pipefd[0]);
close(pipefd[1]);
char *args[] = {"/bin/ls", NULL};
execve("/bin/ls", args, NULL);
perror("execve ls");
_exit(1);
}
// תהליך בן שני - מריץ wc -l
pid_t pid2 = fork();
if (pid2 == 0) {
dup2(pipefd[0], 0); // stdin -> צד הקריאה של הצינור
close(pipefd[0]);
close(pipefd[1]);
char *args[] = {"/usr/bin/wc", "-l", NULL};
execve("/usr/bin/wc", args, NULL);
perror("execve wc");
_exit(1);
}
// האב סוגר את הצינור ומחכה
close(pipefd[0]);
close(pipefd[1]);
wait(NULL);
wait(NULL);
return 0;
}
שימו לב שהאב חייב לסגור את שני צדדי הצינור! אם האב לא יסגור את pipefd[1], wc לעולם לא יקבל EOF ויתקע.
פתרון 4 - תקשורת דרך FIFO¶
כתבו שתי תוכנות נפרדות שמתקשרות דרך FIFO.
writer.c:
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
int main() {
// יוצרים FIFO (לא נורא אם כבר קיים)
mkfifo("/tmp/my_fifo", 0644);
printf("writer: waiting for reader to connect...\n");
int fd = open("/tmp/my_fifo", O_WRONLY);
printf("writer: connected!\n");
const char *msg = "hello from writer!\n";
write(fd, msg, strlen(msg));
printf("writer: message sent\n");
close(fd);
unlink("/tmp/my_fifo"); // מוחקים את הFIFO
return 0;
}
reader.c:
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
printf("reader: waiting for writer to connect...\n");
int fd = open("/tmp/my_fifo", O_RDONLY);
printf("reader: connected!\n");
char buf[128];
int n = read(fd, buf, sizeof(buf) - 1);
buf[n] = '\0';
printf("reader received: %s", buf);
close(fd);
return 0;
}
כדי לבדוק:
1. קמפלו את שתי התוכנות: gcc writer.c -o writer ו-gcc reader.c -o reader
2. בטרמינל ראשון הריצו: ./writer
3. בטרמינל שני הריצו: ./reader
הopen יחסום עד ששני הצדדים מחוברים, ואז הנתונים יזרמו.
פתרון 5 - שרשרת של שלושה תהליכים¶
כתבו תוכנית שיוצרת שרשרת: תהליך 1 כותב מספרים 1-20, תהליך 2 מסנן זוגיים, תהליך 3 מדפיס.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main() {
int pipe1[2]; // צינור בין תהליך 1 לתהליך 2
int pipe2[2]; // צינור בין תהליך 2 לתהליך 3
pipe(pipe1);
pipe(pipe2);
// תהליך 1 - מייצר מספרים 1-20
pid_t pid1 = fork();
if (pid1 == 0) {
close(pipe1[0]); // לא קורא מצינור 1
close(pipe2[0]); // לא קשור לצינור 2
close(pipe2[1]);
for (int i = 1; i <= 20; i++) {
char buf[16];
sprintf(buf, "%d\n", i);
write(pipe1[1], buf, strlen(buf));
}
close(pipe1[1]);
_exit(0);
}
// תהליך 2 - מסנן מספרים זוגיים
pid_t pid2 = fork();
if (pid2 == 0) {
close(pipe1[1]); // לא כותב לצינור 1
close(pipe2[0]); // לא קורא מצינור 2
// ממירים fd ל-FILE* לנוחות קריאת שורות
FILE *in = fdopen(pipe1[0], "r");
char line[64];
while (fgets(line, sizeof(line), in)) {
int num = atoi(line);
if (num % 2 == 0) {
write(pipe2[1], line, strlen(line));
}
}
fclose(in);
close(pipe2[1]);
_exit(0);
}
// תהליך 3 - מדפיס למסך
pid_t pid3 = fork();
if (pid3 == 0) {
close(pipe1[0]); // לא קשור לצינור 1
close(pipe1[1]);
close(pipe2[1]); // לא כותב לצינור 2
char buf[64];
int n;
while ((n = read(pipe2[0], buf, sizeof(buf) - 1)) > 0) {
buf[n] = '\0';
printf("%s", buf);
}
close(pipe2[0]);
_exit(0);
}
// האב סוגר הכל ומחכה
close(pipe1[0]);
close(pipe1[1]);
close(pipe2[0]);
close(pipe2[1]);
wait(NULL);
wait(NULL);
wait(NULL);
return 0;
}
הפלט הצפוי:
נקודות חשובות:
- יצרנו שני צינורות כי יש שלושה תהליכים בשרשרת
- כל תהליך חייב לסגור את כל הfd-ים שהוא לא משתמש בהם
- בתהליך 2 השתמשנו ב-fdopen כדי להמיר fd ל-FILE* - ככה יכולנו להשתמש ב-fgets שקוראת שורה שלמה. זה דוגמה טובה לשילוב בין fd-ים לFILE* כשזה נוח
- האב חייב לסגור את כל ארבעת הfd-ים של הצינורות, אחרת התהליכים ייתקעו