Potoki - Bad file descriptor

Potoki - Bad file descriptor
NO
  • Rejestracja:ponad 7 lat
  • Ostatnio:prawie 7 lat
  • Postów:56
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

Kopiuj
#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:

Kopiuj
#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:

Kopiuj
#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
KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
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.
edytowany 2x, ostatnio: kapojot
NO
  • Rejestracja:ponad 7 lat
  • Ostatnio:prawie 7 lat
  • Postów:56
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?
KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
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 :)

KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
1

Zerknij na poniższy kod.

Kopiuj
#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]));
}
NO
  • Rejestracja:ponad 7 lat
  • Ostatnio:prawie 7 lat
  • Postów:56
0

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

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

Oraz co oznacza nazwa zmiennej "ret"?

KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
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.

edytowany 2x, ostatnio: kapojot
NO
  • Rejestracja:ponad 7 lat
  • Ostatnio:prawie 7 lat
  • Postów:56
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?

KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
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.

NO
  • Rejestracja:ponad 7 lat
  • Ostatnio:prawie 7 lat
  • Postów:56
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ę.

Kopiuj
	  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);
edytowany 2x, ostatnio: nojaniewiem
KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
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ą.

edytowany 1x, ostatnio: kapojot
NO
  • Rejestracja:ponad 7 lat
  • Ostatnio:prawie 7 lat
  • Postów:56
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:

Kopiuj
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?

AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:około 3 lata
  • Postów:1493
1

Np. z wykorzystaniem sprintf albo itoa.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)