2010-03-27 60 views
59

Hệ điều hành: Linux, Ngôn ngữ: tinh khiết Cprintf bất thường sau "fork()"

Tôi đang tiến lên trong việc học lập trình C nói chung và lập trình C trong UNIX trong trường hợp đặc biệt.

Tôi đã phát hiện thấy hành vi lạ (đối với tôi) của hàm printf() sau khi sử dụng cuộc gọi fork().

#include <stdio.h> 
#include <system.h> 

int main() 
{ 
    int pid; 
    printf("Hello, my pid is %d", getpid()); 

    pid = fork(); 
    if(pid == 0) 
    { 
      printf("\nI was forked! :D"); 
      sleep(3); 
    } 
    else 
    { 
      waitpid(pid, NULL, 0); 
      printf("\n%d was forked!", pid); 
    } 
    return 0; 
} 

Output

Hello, my pid is 1111 
I was forked! :DHello, my pid is 1111 
2222 was forked! 

Tại sao thứ hai "Hello" chuỗi xảy ra ở đầu ra của trẻ?

Có, đó chính xác là những gì cha mẹ đã in khi bắt đầu, với bố mẹ pid.

Nhưng! Nếu chúng ta đặt một nhân vật \n vào cuối mỗi chuỗi chúng tôi nhận được đầu ra dự kiến:

#include <stdio.h> 
#include <system.h> 

int main() 
{ 
    int pid; 
    printf("Hello, my pid is %d\n", getpid()); // SIC!! 

    pid = fork(); 
    if(pid == 0) 
    { 
      printf("I was forked! :D"); // removed the '\n', no matter 
      sleep(3); 
    } 
    else 
    { 
      waitpid(pid, NULL, 0); 
      printf("\n%d was forked!", pid); 
    } 
    return 0; 
} 

Output:

Hello, my pid is 1111 
I was forked! :D 
2222 was forked! 

Tại sao nó xảy ra? Đó là hành vi đúng hay là một lỗi?

Trả lời

75

Tôi lưu ý rằng <system.h> là tiêu đề không chuẩn; Tôi đã thay thế nó bằng <unistd.h> và mã được biên dịch một cách rõ ràng.

Khi đầu ra của chương trình của bạn đang đi đến một thiết bị đầu cuối (màn hình), nó là dòng đệm. Khi đầu ra của chương trình của bạn đi đến một đường ống, nó được đệm đầy đủ. Bạn có thể kiểm soát chế độ đệm theo chức năng Chuẩn C setvbuf() và chế độ _IOFBF (toàn bộ bộ đệm), _IOLBF (đường đệm) và _IONBF (không đệm).

Bạn có thể chứng minh điều này trong chương trình đã sửa đổi của mình bằng cách tạo đầu ra cho chương trình của bạn, ví dụ: cat. Ngay cả với các dòng mới ở cuối các chuỗi printf(), bạn sẽ thấy thông tin kép. Nếu bạn gửi nó trực tiếp đến nhà ga, thì bạn sẽ chỉ thấy rất nhiều thông tin.

Đạo đức của câu chuyện là phải cẩn thận gọi fflush(0); để làm trống tất cả bộ đệm I/O trước khi tắt.


Line-by-line phân tích, theo yêu cầu (niềng răng vv loại bỏ - và không gian hàng đầu loại bỏ bằng cách biên tập đánh dấu):

  1. printf("Hello, my pid is %d", getpid());
  2. pid = fork();
  3. if(pid == 0)
  4. printf("\nI was forked! :D");
  5. sleep(3);
  6. else
  7. waitpid(pid, NULL, 0);
  8. printf("\n%d was forked!", pid);

Phân tích:

  1. Bản sao "Xin chào, tôi là pid 1234" vào bộ đệm cho đầu ra tiêu chuẩn. Bởi vì không có dòng mới ở cuối và đầu ra đang chạy ở chế độ dòng đệm (hoặc chế độ đệm đầy), không có gì xuất hiện trên thiết bị đầu cuối.
  2. Cung cấp cho chúng tôi hai quy trình riêng biệt, với chính xác cùng một tài liệu trong bộ đệm stdout.
  3. Đứa trẻ có pid == 0 và thực hiện các dòng 4 và 5; cha mẹ có giá trị khác 0 cho pid (một trong số ít sự khác biệt giữa hai quy trình - giá trị trả về từ getpid()getppid() là hai giá trị khác).
  4. Thêm dòng mới và "Tôi đã được chia hai!: D" vào bộ đệm đầu ra của con. Dòng đầu tiên xuất hiện trên thiết bị đầu cuối; phần còn lại được giữ trong bộ đệm vì đầu ra là dòng đệm.
  5. Mọi thứ sẽ tạm dừng trong 3 giây. Sau đó, đứa trẻ đi ra bình thường thông qua sự trở lại vào cuối của chính. Tại thời điểm đó, dữ liệu dư trong bộ đệm stdout bị xóa. Điều này rời khỏi vị trí đầu ra ở cuối của một dòng vì không có dòng mới.
  6. Phụ huynh đến đây.
  7. Cha mẹ đợi đứa trẻ kết thúc chết.
  8. Cha mẹ thêm dòng mới và "1345 đã được chia nhỏ!" vào bộ đệm đầu ra. Dòng mới tuôn ra thông điệp 'Hello' đến đầu ra, sau dòng chưa hoàn chỉnh được tạo ra bởi đứa trẻ.

Cha mẹ hiện đang thoát bình thường thông qua việc trả lại ở cuối chính và dữ liệu còn lại bị xóa; vì vẫn không có dòng mới ở cuối, vị trí con trỏ nằm sau dấu chấm than và dấu nhắc trình bao xuất hiện trên cùng một dòng.

gì tôi thấy là:

Osiris-2 JL: ./xx 
Hello, my pid is 37290 
I was forked! :DHello, my pid is 37290 
37291 was forked!Osiris-2 JL: 
Osiris-2 JL: 

Những con số PID là khác nhau - nhưng sự xuất hiện tổng thể là rõ ràng.Thêm dòng mới vào cuối printf() báo cáo (mà trở thành tiêu chuẩn thực hiện rất nhanh chóng) làm thay đổi sản lượng nhiều:

#include <stdio.h> 
#include <unistd.h> 

int main() 
{ 
    int pid; 
    printf("Hello, my pid is %d\n", getpid()); 

    pid = fork(); 
    if(pid == 0) 
     printf("I was forked! :D %d\n", getpid()); 
    else 
    { 
     waitpid(pid, NULL, 0); 
     printf("%d was forked!\n", pid); 
    } 
    return 0; 
} 

bây giờ tôi nhận được:

Osiris-2 JL: ./xx 
Hello, my pid is 37589 
I was forked! :D 37590 
37590 was forked! 
Osiris-2 JL: ./xx | cat 
Hello, my pid is 37594 
I was forked! :D 37596 
Hello, my pid is 37594 
37596 was forked! 
Osiris-2 JL: 

Chú ý rằng khi đầu ra đi vào nhà ga , nó là dòng đệm, vì vậy dòng 'Hello' xuất hiện trước fork() và chỉ có một bản sao. Khi đầu ra được chuyển đến cat, nó được đệm đầy đủ, vì vậy không có gì xuất hiện trước fork() và cả hai quá trình đều có dòng 'Hello' trong bộ đệm được xóa.

+0

Ok, tôi hiểu rồi. Nhưng tôi vẫn không thể giải thích cho chính mình tại sao "rác đệm" xuất hiện ở cuối dòng mới được in ra trong đầu ra của đứa trẻ? Nhưng chờ đã, bây giờ tôi nghi ngờ rằng nó thực sự là đầu ra của CHILD .. oh, bạn có thể giải thích tại sao đầu ra trông CHÍNH XÁC (chuỗi mới TRƯỚC cái cũ) như thế, từng bước, vì vậy tôi sẽ rất biết ơn. Cảm ơn bạn! – pechenie

+0

Giải thích rất ấn tượng! Cảm ơn bạn rất nhiều, cuối cùng tôi đã hiểu rõ! P .: Tôi đã bỏ phiếu cho bạn trước đây và giờ tôi đã nhấp vào "mũi tên lên" một cách ngu ngốc một lần nữa, vì vậy bỏ phiếu đã biến mất. Nhưng tôi không thể đưa lại cho bạn một lần nữa vì "câu trả lời quá cũ": ( P.P.S .: Tôi đã bỏ phiếu cho bạn trong câu hỏi khác. Và cảm ơn bạn một lần nữa! – pechenie

22

Lý do tại sao không có \n ở cuối chuỗi định dạng giá trị không được in ngay lập tức lên màn hình. Thay vào đó nó được đệm trong quá trình. Điều này có nghĩa là nó không thực sự được in cho đến sau khi hoạt động ngã ba do đó bạn nhận được nó in hai lần.

Thêm \n mặc dù buộc bộ đệm bị xóa và xuất ra màn hình. Điều này xảy ra trước ngã ba và do đó chỉ được in một lần.

Bạn có thể buộc điều này xảy ra bằng cách sử dụng phương thức fflush. Ví dụ:

printf("Hello, my pid is %d", getpid()); 
fflush(stdout); 
+0

Cảm ơn bạn đã trả lời! – pechenie

+0

'fflush (stdout);' Có vẻ là câu trả lời chính xác hơn ở đây imo. –

5

fork() tạo bản sao quy trình một cách hiệu quả. Nếu trước khi gọi fork(), dữ liệu đã được lưu vào bộ đệm, cả cha và mẹ sẽ có cùng dữ liệu được lưu vào bộ đệm. Lần sau mỗi người trong số họ làm một cái gì đó để tuôn ra bộ đệm của nó (chẳng hạn như in một dòng mới trong trường hợp đầu ra thiết bị đầu cuối), bạn sẽ thấy đầu ra đệm ngoài bất kỳ đầu ra mới nào được tạo ra bởi quá trình đó. Vì vậy, nếu bạn sẽ sử dụng stdio trong cả cha và con thì bạn nên fflush trước khi forking, để đảm bảo rằng không có dữ liệu đệm.

Thông thường, trẻ chỉ được sử dụng để gọi hàm exec*. Kể từ đó thay thế hình ảnh quá trình con hoàn chỉnh (bao gồm cả bất kỳ bộ đệm) có kỹ thuật không cần phải fflush nếu đó thực sự là tất cả những gì bạn sẽ làm ở trẻ em. Tuy nhiên, nếu có thể có dữ liệu đệm thì bạn nên cẩn thận trong cách xử lý lỗi exec. Đặc biệt, tránh in lỗi tới stdout hoặc stderr bằng bất kỳ chức năng stdio nào (write là ok), sau đó gọi _exit (hoặc _Exit) thay vì gọi exit hoặc chỉ trở lại (sẽ xóa bất kỳ đầu ra đệm nào). Hoặc tránh vấn đề hoàn toàn bằng cách xả trước khi forking.

Các vấn đề liên quan