Na starszego systemu RHEL mam, /bin/catczy nie pętli dla cat x >> x. catwyświetla komunikat o błędzie „cat: x: plik wejściowy to plik wyjściowy”. Mogę oszukać /bin/catw 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 catprocesu 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 stdouti fkodu), 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()nastdoutmoż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 stracena RHEL cat- po prostu robi kolejne read()i write()systemowe wywołania. Ale catnie 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.