Potoki - Bad file descriptor

0

Witam. Piszę program mający być rozwiązaniem problemu producenta i konsumenta z użyciem potoków nienazwanych. Niestety, na swojej drodze natrafiłem na błąd, którego nie jestem w stanie rozwiązać. Program kompiluje się, ale uruchomienie skutkuje wyświetleniem dla obu procesów błędu "Bad file descriptor". Próby doprowadziły mnie do wniosku, że problem tkwi w potoku. Szukałem rozwiązania w internecie, porównywałem sposoby użycia funkcji read() oraz write(), lecz wciąż nie wiem, co dokładnie jest nie tak. Proszę o pomoc, co robię źle, gdzie popełniłem błąd?

Poniżej kod programu:
prod.c

#include "lib.h"

int main()
{
	int desk1=open("./plik1", O_RDONLY, 0644);
	int buf[50];
	if (desk1==-1)
	{
		printf("Nie da rady...\n");
		exit(3);
	}
	//deskryptor pliku producenta

	const int child_pid=fork();
	//utworzenie procesu potomnego

	if(pipe(fd)==-1)
	{
		perror("Blad funkcji pipe()\n");
		exit(1);
	}

	switch(child_pid)
	{
		case -1:
		  perror("Blad funkcji fork(). Cos poszlo nie tak...\n");
		  exit(2);
		  break;
		case 0:
		//sleep(1);
		  execl("./kons.x", "kons.x", NULL);
		  perror("Blad fukcji exec().\n");
		default:
			close(fd[1]);

		while(read(desk1, buf, 50))
		{
		  if(write(fd[0], buf, PIPE_BUF)<0)
			{
				perror("Nie odczytam...\n");
				exit(4);
			}
		}
		close(fd[0]);
			wait(NULL);
		  break;
	}
return 0;
}

kons.c:

#include "lib.h"

int main()
{
	char buf[50];
	int desk2=open("./plik2", O_WRONLY, 0644);
	if(desk2==-1)
		{
			perror("Nie moge otworzyc plik2...\n");
			exit(6);
		}
	//sleep(1);
	close(fd[0]);
	while(read(fd[1], buf, 50))
	{
	if(read(buf, &desk2, 50)==-1)
		{
			perror("Nie moge zapisac...\n");
			exit(5);
		}
	}
	close(fd[1]);
return 0;
}

lib.h:

#ifndef LIB_H
#define LIB_H

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <limits.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>

	int fd[2];

#endif
1
  1. Nie sprawdzasz, czy open(2) zwraca poprawny deskryptor pliku, a nie na przykład błąd.
  2. To samo w przypadku fork(2), który też może zwrócić błąd.
  3. int fd[2]; w pliku .h to słabe rozwiązanie.
  4. De facto nie używasz potoków nienazwanych. man 2 pipe.
  5. Jak zwykle komplikujesz niepotrzebnie kod.
  6. Otwierasz plik, zapisujesz jego fd do jednej zmiennej, po czym natychmiast zamykasz inną...
  7. fd[2] jest w przypadku drugiego programu niezainicjalizowane. exec(2) zamknęło wszystkie FD poza 0,1,2.
0

@kapojot:

  1. Rozumiem, że warunek if(desk1==-1) jest niepoprawny?
  2. Podobnie, jak case -1?
  3. Niestety, był to jedyny sposób, by kompilator nie zwracał błędu
  4. Zajrzałem do man, jednak nie widzę różnicy pomiędzy podanym tam przykładem, a moim użyciem funkcji pipe(). Mógłbyś wyjaśnić, co na tym etapie robię źle?
1

Hmm, odnośnie 1 i 2 - przeoczyłem, za dużo kodu na dziś.
Odnośnie 3 - przerobię ten kod "po swojemu", to znowu podyskutujemy, jak to można inaczej rozwiązać.
Odnośnie 4 - tak, wołasz pipe po a nie przed forkiem :)

1

Zerknij na poniższy kod.

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <err.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
parent(int fd_to_child)
{
	int desk1;
	char buf[50];
	ssize_t bytes_read, bytes_written;

	desk1 = open("./plik1", O_RDONLY);
	if (desk1 < 0)
		err(2, "open(2) failed.");

	do {
		bytes_read = read(desk1, buf, sizeof(buf));
		if (bytes_read < 0)
			err(7, "read(2) failed.");

		bytes_written = 0;
		while (bytes_written < bytes_read) {
			ssize_t ret;

			ret = write(fd_to_child, buf + bytes_written,
			    bytes_read - bytes_written);
			if (ret < 0)
				err(4, "write(2) failed.");

			bytes_written += ret;
		}
	} while (bytes_read > 0);

	close(desk1);
	close(fd_to_child);
	wait(NULL);

	return (0);
}

int
child(int fd_from_parent)
{
	int desk2;
	char buf[50];
	ssize_t bytes_read, bytes_written;

	desk2 = open("./plik2", O_WRONLY | O_CREAT, 0644);
	if (desk2 == -1)
		err(6, "open(2) failed.");

	do {
		bytes_read = read(fd_from_parent, buf, sizeof(buf));
		if (bytes_read < 0)
			err(5, "read(2) failed.");

		bytes_written = 0;
		while (bytes_written < bytes_read) {
			ssize_t ret;

			ret = write(desk2, buf + bytes_written, bytes_read -
			    bytes_written);
			if (ret < 0)
				err(5, "write(2) failed.");

			bytes_written += ret;
		}

	} while (bytes_read > 0);

	close(desk2);
	close(fd_from_parent);

	return (0);
}

int
main()
{
	int fd[2];
	pid_t child_pid;

	if (pipe(fd) == -1) {
		perror("Blad funkcji pipe()\n");
		exit(1);
	}

	child_pid = fork();
	if (child_pid == -1)
		err(2, "fork(2) failed");

	if (child_pid == 0) {
		close(fd[0]);
		return (child(fd[1]));
	}

	close(fd[1]);
	return (parent(fd[0]));
}
0

@kapojot: Dzięki wielkie. Mógłbyś jeszcze wyjaśnić mi, dlaczego funkcja write została zapisana w taki sposób:

            ret = write(fd_to_child, buf + bytes_written,
                bytes_read - bytes_written);

Oraz co oznacza nazwa zmiennej "ret"?

1

Zmienną pomocniczą, która służy do sprawdzenia, co zwróciła funkcja write.

A teraz dłuższe wyjaśnienie, czemu tam znalazła się taka pato-litania. write (podobnie zresztą jak read) mogą - uwaga - zwrócić wczytaną liczbę bajtów mniejszą niż rozmiar podanego bufora. Oznacza to, że np. masz bufor 50 bajtów, ale write zdołał zapisać tylko 15 z nich. Co należy zrobić? Ponowić write, podając tym razem przesunięty wskaźnik o liczbę zapisanych uprzednio bajtów oraz ilość bajtów do zapisania pomniejszoną o te już zapisane. Poczytaj dokładnie manual do obu tych funkcji. To dość częsty błąd popełniany przez osoby zaczynające programować w C.

0

Właśnie zauważyłem, że próba wypisania na ekran bezpośrednio z bufora skutkuje otrzymaniem tekstu wraz z śmieciami. Mógłbyś mi podpowiedzieć, jak wypisać zawartość bufora z ich pominięciem?

1

Zapewne nie masz terminatora po danych wczytanych z read. Powiększ bufor o 1 znak i po odczycie n bajtów umieść '\0' w miejscu n+1.

0

Postanowiłem spróbować zrobić to w poniższy sposób (poniższy fragment kodu wrzuciłem do pliku konsumenta, zaraz za przypisaniem zmiennej ret wartości funkcji write()). Czy taki sposób jest prawidłowy? Dodam, że wypisywane jest dokładnie to, co chcę.

	  char text[(bytes_read-bytes_written)+1];
	  int i=0;
	  for(i=0; i<bytes_read-bytes_written; i++)
	    {
	      text[i]=buf[i];
	    }
	  text[bytes_read-bytes_written]='\0';
	  printf("%s", text);
1

Przekombinowałeś. Nawet, jeśli działa, to jest zbyt skomplikowane - spróbuj wymyśleć prostszy sposób.

bytes_read - bytes_written aż się prosi o dodatkową zmienną lokalną.

0

@kapojot: Jeszcze raz, wielkie dzięki za pomoc. Mam jeszcze jedno pytanie, na które niestety nigdzie nie znalazłem odpowiedzi. Dowiedziałem się, że ta linijka w moim kodzie:

execl(CHLD_PATH, CHLD_N, &fd[0], &fd[1], argv[2], NULL);

jest nieprawidłowa i powinienem deskryptory przekazać jako stałą tekstową. Czy w ogóle jest to wykonalne?

1

Np. z wykorzystaniem sprintf albo itoa.

1 użytkowników online, w tym zalogowanych: 0, gości: 1