Na starszego systemu RHEL mam, /bin/cat
czy nie pętli dla cat x >> x
. cat
wyświetla komunikat o błędzie „cat: x: plik wejściowy to plik wyjściowy”. Mogę oszukać /bin/cat
w ten sposób: cat < x >> x
. Gdy wypróbuję powyższy kod, pojawia się opisany „zapętlenie”. Napisałem też „cat” oparty na wywołaniach systemowych:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int
main(int ac, char **av)
{
char buf[4906];
int fd, cc;
fd = open(av[1], O_RDONLY);
while ((cc = read(fd, buf, sizeof(buf))) > 0)
if (cc > 0) write(1, buf, cc);
close(fd);
return 0;
}
To również pętle. Jedyne buforowanie tutaj (w przeciwieństwie do „mycat” na stdio) jest tym, co dzieje się w jądrze.
Myślę, że dzieje się tak, że deskryptor pliku 3 (wynik open(av[1])
) ma przesunięcie do pliku 0. Przesunięty deskryptor 1 (stdout) ma przesunięcie 3, ponieważ „>>” powoduje, że powłoka wywołująca wykonuje lseek()
polecenie deskryptor pliku przed przekazaniem go do cat
procesu potomnego.
Robienie read()
dowolnego rodzaju, czy to w buforze standardowym, czy zwykłym, char buf[]
przesuwa pozycję deskryptora pliku 3. Robienie write()
przesunięcia pozycji deskryptora pliku 1. Te dwa przesunięcia są różnymi liczbami. Z powodu „>>” deskryptor pliku 1 zawsze ma przesunięcie większe lub równe przesunięciu deskryptora pliku 3. Tak więc każdy program „podobny do kota” zapętli się, chyba że wykona jakieś buforowanie wewnętrzne. Możliwe, a może nawet prawdopodobne, że implementacja standardu FILE *
(który jest typem symboli stdout
i f
kodu), która zawiera własny bufor. fread()
może faktycznie wykonać wywołanie systemowe, read()
aby wypełnić wewnętrzny bufor fo f
. To może, ale nie musi, zmienić niczego w środku stdout
. dzwoniąc fwrite()
nastdout
może, ale nie musi, zmieniać niczego w środku f
. Dlatego „kot” oparty na standardzie może nie zapętlić się. Lub może. Trudno powiedzieć bez przeczytania dużo brzydkiego, brzydkiego kodu libc.
Zrobiłem strace
na RHEL cat
- po prostu robi kolejne read()
i write()
systemowe wywołania. Ale cat
nie musi tak działać. mmap()
Plik wejściowy byłby wtedy możliwy write(1, mapped_address, input_file_size)
. Jądro wykona całą pracę. Lub możesz wykonać sendfile()
wywołanie systemowe między deskryptorami plików wejściowych i wyjściowych w systemach Linux. Mówi się, że stare systemy SunOS 4.x wykonują sztuczkę mapowania pamięci, ale nie wiem, czy ktokolwiek kiedykolwiek stworzył kota opartego na plikach send. W obu przypadkach „zapętlenie” nie stało, jak zarówno write()
i sendfile()
wymaga parametru długości do transferu.