Я собирался написать оболочку с языком C. Ниже приведен исходный код:
#include #include #include #include #include int getcmd(char *buf, int nbuf) { memset(buf, 0, nbuf); fgets(buf, nbuf, stdin); printf("pid: %d, ppid: %d\n", getpid(), getppid()); printf("buf: %s", buf); if(buf[0] == 0) {// EOF printf("end of getcmd\n"); return -1; } return 0; } int main(void) { static char buf[100]; int fd, r, ret; // Read and run input commands. while((ret = getcmd(buf, sizeof(buf))) >= 0){ if(fork() == 0) exit(0); wait(&r); } exit(0); }
Когда я исполняю скомпилированный исполняемый файл с redirectм stdin в файл с именем t.sh, который имеет значение «1111 \ n2222 \ n», например ./myshell <t.sh, вывод:
pid: 2952, ppid: 2374 buf: 1111 pid: 2952, ppid: 2374 buf: 2222 pid: 2952, ppid: 2374 buf: 2222 pid: 2952, ppid: 2374 buf: end of getcmd
Очевидно, функция getcmd () получает 3 строки (1111, 2222, 2222), тогда как в t.sh. И эта ситуация становится еще хуже при размещении большего количества строк в t.sh.
И основной процесс – это единственный процесс, выполняемый getcmd, который мы можем сказать по выводу pid.
Кстати, я считаю, что если строка кода wait (& r) удалена, выход может стать нормальным.
wait
гарантирует, что дочерний процесс получит время для запуска до того, как родитель закончит файл. Если я strace
файл под Linux, я получаю
% strace -f ./a.out [lots of stuff] wait4(-1, strace: Process 29317 attached [pid 29317] lseek(0, -2, SEEK_CUR) = 0 [pid 29317] exit_group(0) = ? [pid 29317] +++ exited with 0 +++ <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 29317 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=29317, si_uid=1000, si_status=0 _utime=0, si_stime=0} --- [lots of stuff]
Детский процесс перематывает стандартный ввод как одну из первых операций после fork
, после чего он будет быстро выйти. В частности, он перематывает назад столько же байтов из streamа, сколько было прочитано в fgets
в буфер, но все еще не используется . libc делает это автоматически после fork. Я также видел, как дочерний процесс смывал stdout
.
Я не уверен, что об этом думать … но ясно, что если вы хотите написать оболочку, вы вообще не должны взаимодействовать со стандартными streamами с помощью
. Если lseek
не произошел, тогда дочерний процесс будет пропущен до 4095 байтов stdin
! Вы всегда должны использовать только read
и write
из
. Кроме того, вам может быть повезло с добавлением следующего вызова в начало main
прежде чем что-либо будет прочитано из stdin
:
if (setvbuf(stdin, NULL, _IONBF, 0) != 0) { perror("setvbuf:"); exit(1); }
Это установит stream stdin
в небуферизованный режим , чтобы он не читал слишком много. Тем не менее, на странице руководства Linux для fgets
говорится:
Не рекомендуется смешивать вызовы с функциями ввода из библиотеки stdio с низкоуровневыми вызовами для чтения (2) для файлового дескриптора, связанного с входным streamом; результаты будут неопределенными и, скорее всего, не то, что вы хотите.
BTW, это не может быть воспроизведено, если stdin
происходит из трубы:
% echo -e '1\n2' | ./a.out pid: 498, ppid: 21285 buf: 1 pid: 498, ppid: 21285 buf: 2 pid: 498, ppid: 21285 buf: end of getcmd
Но, естественно, это делает другую проблему видимой – что ребенок видит, что вход пропускается.
PS
Вы никогда не проверяете возвращаемое значение fgets
чтобы не знать, когда возникает ошибка чтения.
Если во время операции возникает ошибка чтения , содержимое массива является неопределенным и возвращается нулевой указатель.