Neden fork () bulunan bir program bazen çıktısını defalarca yazdırıyor?


50

Program 1'de Hello worldyalnızca bir kez basılır, ancak kaldırıp \nçalıştırdığımda (Program 2), çıktı 8 kez basılır. Birisi lütfen bana \nburada önemini ve bunun nasıl etkilendiğini açıklayabilir fork()mi?

Program 1

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello world...\n");
    fork();
    fork();
    fork();
}

Çıkış 1:

hello world... 

2. Program

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello world...");
    fork();
    fork();
    fork();
}

Çıkış 2:

hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...

10
Program 1'i bir dosya ( ./prog1 > prog1.out) veya bir boruya ( ./prog1 | cat) çıkışıyla çalıştırmayı deneyin . Aklını uçurmaya hazır ol. :-) ⁠
G-Man 'Monica’yı Yeniden

Bu konunun başka bir türünü kapsayan ilgili Q + A: C sistemi (“bash”) stdin'i görmezden geliyor
Michael Homer

13
Bu, bazı yakın oylar topladı, bu yüzden şu konuda bir yorum yapıldı: "UNIX C API ve Sistem Arayüzleri" ile ilgili sorulara açıkça izin veriliyor . Arabelleğe alma sorunları kabuk komut dosyalarında da sık karşılaşılan bir durumdur ve fork()biraz da unix'e özgüdür, bu nedenle unix için konuyla ilgili bir konu olduğu anlaşılmaktadır.
ilkkachu

@ ilkkachu aslında, eğer bu bağlantıyı okursanız ve ifade ettiği meta soruyu tıklarsanız, bunun konu dışı olduğu açıkça anlaşılır. Sadece bir şeyin C olması ve unix'in C olması, konuyla ilgili bir şey yapmaz.
Patrick

@Patrick, aslında yaptım. Ve hala "mantık içinde" yan tümcesine uyduğunu düşünüyorum, ama elbette bu sadece benim.
ilkkachu

Yanıtlar:


93

C kütüphanesinin printf()işlevini kullanarak standart çıktıya çıktı yaparken , çıktı genellikle tamponlanır. Siz yeni bir satır çıkana kadar fflush(stdout), programa çağrı yapana veya programdan çıkana kadar arabellek boşalmaz _exit(). Standart çıkış akışı, varsayılan olarak bir TTY'ye bağlandığında bu şekilde varsayılan olarak arabelleğe alınır.

İşlemi "Program 2" de yaptığınızda, alt işlemler, açılmamış çıktı tamponu dahil ana sürecin her bölümünü devralır. Bu, temizlenmemiş tamponu her çocuk işlemine etkili bir şekilde kopyalar.

İşlem sonlandırıldığında tamponlar temizlenir. Toplamda sekiz işlem (orijinal işlem dahil) başlatırsınız ve temizlenmemiş tampon her bir işlemin sonunda temizlenir.

Bu var sekiz her biri çünkü fork()iki kez daha önce vardı süreçlerin sayısını elde fork()(bunlar koşulsuz olduğundan) ve bu (2 Üç tane 3 = 8).


14
İlgili: Eğer bitirebilirsiniz mainile _exit(0)sadece ateş basması tampon olmadan bir çıkış sistemi arama yapmak ve sonra yeni bir satırdan olmadan sıfır kez basılacaktır için. ( Syscall exit () ve How come _exit (0) (syscall'dan çıkma) herhangi bir stdout içeriği almamı engeller mi? ). Veya Program1'i catbir dosyaya yönlendirebilir veya bir dosyaya yönlendirebilir ve 8 kez basıldığını görebilirsiniz. (stdout, TTY olmadığında varsayılan olarak tam arabelleğe alınır). Ya da fflush(stdout)2'den önce yeni hat olmayan bir davaya bir fork()...
Peter Cordes

17

Çatalı hiçbir şekilde etkilemez.

İlk durumda, hiçbir şey yazmadan 8 işlemle sonuçlanır, çünkü çıktı tamponu zaten boşaltılmıştır (nedeniyle \n).

İkinci durumda, her biri "Merhaba dünya ..." içeren bir arabellek içeren ve her biri işlem sonunda yazılan 8 işleminiz var.


12

@Kusalananda, çıkışın neden tekrarlandığını açıkladı . Çıkış tekrarlanır neden merak ediyorsanız 8 kat ve sadece 4 kez (baz programı + 3 çatal):

int main()
{
    printf("hello world...");
    fork(); // here it creates a copy of itself --> 2 instances
    fork(); // each of the 2 instances creates another copy of itself --> 4 instances
    fork(); // each of the 4 instances creates another copy of itself --> 8 instances
}

2
Bu çatal temel olduğunu
Prvt_Yadav

3
@Debian_yadav muhtemelen sadece onun anlamlarına aşina iseniz açıktır. Örneğin kızarma stdio tamponları gibi.
roaima

2
@Debian_yadav: en.wikipedia.org/wiki/False_consensus_effect - Eğer herkes her şeyi biliyorsa neden sorular sormalıyız?
Honza Zidek

8
@Debian_yadav OP'nin aklını okuyamıyorum, bu yüzden bilmiyorum. Her neyse, stackexchange, başkalarının da bilgi aradığı bir yer ve cevabımın Kulasandra'nın iyi cevabına faydalı bir katkı olabileceğini düşünüyorum. Cevabım , Kulasandra'nın kendisinden 2 saat önce söylediklerini tekrarlayan edc65'inkiyle karşılaştırıldığında (basit ama kullanışlı) bir şeyler ekliyor .
Honza Zidek

2
Bu sadece bir cevap için kısa bir yorum, gerçek bir cevap değil. Soru, tam olarak neden 8 değil de “çoklu zamanlar” hakkında soru soruyor.
pipe

3

Buradaki önemli arka plan , standart olarak varsayılan ayar olarak satır tamponlustdout olması gerekir .

Bu \n, çıktının yıkanmasına neden olur .

İkinci örnek newline'ı içermediğinden, çıktı boşaltılmaz ve fork()tüm işlemi kopyaladığında, stdouttamponun durumunu da kopyalar .

Şimdi, fork()örneğinizdeki bu çağrılar toplamda 8 işlem yaratıyor - hepsi stdouttamponun bir kopyasıyla birlikte .

Tanım olarak, tüm bu işlemler exit()geri dönerken çağrılırmain() ve ardından tüm aktif stdio akışlarında exit()çağrılar çağrılır . Bu, içerir ve sonuç olarak, aynı içeriği sekiz kez görürsünüz.fflush()fclose()stdout

fflush()Çağrılmadan önce bekleyen çıktıları olan tüm akışları çağırmak fork()ya da çatallı çocuğun açıkça _exit()stdio akışlarını temizlemeden yalnızca süreçten çıkmasını sağlamak için iyi bir uygulamadır .

exec()Aramanın stdio arabelleklerini temizlemediğini unutmayın , bu nedenle (aradıktan sonra fork()) aramayı exec()ve (başarısız olursa) aramayı stdio arabelleklerini umursamayacağınıza dikkat edin _exit().

BTW: Yanlış tamponlamanın neden olabileceğini anlamak için, Linux'ta yakın zamanda düzeltilmiş olan eski bir hata:

Standart, stderrvarsayılan olarak arabelleksiz hale getirilmeyi gerektirir , ancak Linux bunu ihmal etti ve stderrstderr'in bir boruya yönlendirilmesi durumunda hattı tamponladı ve (daha da kötüsü) tamamen tamponladı. Bu yüzden UNIX için yazılmış programlar Linux'ta çok geç yeni satır olmadan bir şeyler çıkardılar.

Aşağıdaki yoruma bakınız, düzeltildi gibi görünüyor.

Bu Linux sorunu etrafında çalışmak için yaptığım şey:

    /* 
     * Linux comes with a broken libc that makes "stderr" buffered even 
     * though POSIX requires "stderr" to be never "fully buffered". 
     * As a result, we would get garbled output once our fork()d child 
     * calls exit(). We work around the Linux bug by calling fflush() 
     * before fork()ing. 
     */ 
    fflush(stderr); 

Bu kod diğer platformlara zarar vermez, çünkü fflush()yalnızca temizlenen bir akıntıya çağrı yapmak bir noop'tur.


2
Hayır, stdout tam olarak tamponlanmalıdır, bu durumda belirtilmemiş olan etkileşimli bir cihaz olmadıkça, ancak pratikte daha sonra satır tamponlanır. stderr'in tamamen tamponlanmaması gerekiyor. Bkz. Pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/…
Stéphane Chazelas

Adamım sayfa için setbuf(), Debian (üzerinde man7.org üzerinde bir benzeyen ), "standart hata akımı stderr için varsayılan olarak tamponsuz olduğunu." Belirtmektedir ve basit bir test, çıkışın bir dosyaya, bir boruya veya bir terminale gitmesine bakılmaksızın, bu şekilde hareket eder gibi görünmektedir. C kütüphanesinin hangi versiyonunun başka türlü yapacağı hakkında bir referansınız var mı?
ilkkachu

4
Linux bir çekirdek, stdio tamponlama bir kullanıcı özelliğidir, çekirdek orada yer almaz. Linux çekirdeği için kullanılabilen bir dizi libc uygulaması vardır, sunucu / iş istasyonu tipi sistemlerde en yaygın olanı stdout'un tam arabelleğe alındığı (tty ise satır arabelleği) ve stderr'in arabelleğe alınmadığı GNU uygulamasıdır.
Stéphane Chazelas

1
@schily, koştuğum sadece test: paste.dy.fi/xk4 . Aynı sonucu çok eski bir sistemle aldım.
ilkkachu

1
@schily Bu doğru değil. Örneğin, bu yorumu musl kullanan Alpine Linux kullanarak yazıyorum.
NieDzejkob
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.