Sự khác biệt giữa fork (), vfork (), exec () và clone ()


197

Tôi đang tìm kiếm sự khác biệt giữa bốn người này trên Google và tôi dự đoán sẽ có một lượng thông tin khổng lồ về vấn đề này, nhưng thực sự không có bất kỳ so sánh chắc chắn nào giữa bốn cuộc gọi.

Tôi bắt đầu cố gắng biên dịch một loại cái nhìn cơ bản trong nháy mắt về sự khác biệt giữa các cuộc gọi hệ thống này và đây là những gì tôi nhận được. Là tất cả thông tin này là chính xác / tôi có thiếu điều gì quan trọng không?

Fork : Cuộc gọi ngã ba về cơ bản tạo ra một bản sao của quy trình hiện tại, giống hệt nhau theo mọi cách (không phải mọi thứ đều được sao chép, ví dụ, giới hạn tài nguyên trong một số triển khai nhưng ý tưởng là tạo ra một bản sao càng gần càng tốt).

Quá trình mới (con) nhận ID tiến trình khác nhau (PID) và có PID của quy trình cũ (cha mẹ) là PID chính (PPID). Bởi vì hai quy trình hiện đang chạy chính xác cùng một mã, chúng có thể cho biết đó là mã trả về của ngã ba nào - đứa trẻ nhận được 0, cha mẹ nhận được PID của đứa trẻ. Tất nhiên, đây là tất cả, giả sử cuộc gọi ngã ba hoạt động - nếu không, không có con nào được tạo và cha mẹ nhận được mã lỗi.

Vfork: Sự khác biệt cơ bản giữa vfork và fork là khi một quy trình mới được tạo bằng vfork (), quy trình cha mẹ tạm thời bị đình chỉ và quy trình con có thể mượn không gian địa chỉ của cha mẹ. Tình trạng kỳ lạ này tiếp diễn cho đến khi tiến trình con thoát ra hoặc gọi execve (), tại đó tiến trình cha mẹ tiếp tục.

Điều này có nghĩa là quy trình con của vfork () phải cẩn thận để tránh các biến bất ngờ của quy trình cha. Cụ thể, tiến trình con không được trả về từ hàm chứa lệnh gọi vfork () và nó không được gọi exit () (nếu cần thoát, nó nên sử dụng _exit (); thực ra, điều này cũng đúng với con của một ngã ba bình thường ()).

Exec :Cuộc gọi exec là một cách cơ bản để thay thế toàn bộ quy trình hiện tại bằng một chương trình mới. Nó tải chương trình vào không gian quy trình hiện tại và chạy nó từ điểm vào. exec () thay thế tiến trình hiện tại bằng một hàm thực thi được trỏ bởi hàm. Kiểm soát không bao giờ trở lại chương trình gốc trừ khi có lỗi exec ().

Clone :Clone, như ngã ba, tạo ra một quy trình mới. Không giống như fork, các cuộc gọi này cho phép tiến trình con chia sẻ các phần trong bối cảnh thực hiện của nó với quá trình gọi, chẳng hạn như không gian bộ nhớ, bảng mô tả tệp và bảng xử lý tín hiệu.

Khi tiến trình con được tạo bằng clone, nó sẽ thực thi ứng dụng hàm fn (arg). (Điều này khác với fork, trong đó việc thực thi tiếp tục ở trẻ em từ điểm của lệnh gọi fork ban đầu.) Đối số fn là một con trỏ tới một hàm được gọi bởi tiến trình con khi bắt đầu thực hiện. Đối số arg được truyền cho hàm fn.

Khi ứng dụng hàm fn (arg) trả về, tiến trình con kết thúc. Số nguyên được trả về bởi fn là mã thoát cho tiến trình con. Quá trình con cũng có thể chấm dứt rõ ràng bằng cách gọi thoát (2) hoặc sau khi nhận được tín hiệu gây tử vong.

Thông tin nhận được mẫu:

Cảm ơn đã dành thời gian để đọc ! :)


2
Tại sao vfork không gọi exit ()? Hay không trở về? Không thoát () chỉ sử dụng _exit ()? Tôi cũng đang cố gắng để hiểu :)
LazerSharks

2
@Gnuey: bởi vì nó có khả năng (nếu nó được triển khai khác với fork()Linux và có lẽ là tất cả các BSD) mượn không gian địa chỉ của cha mẹ. Bất cứ điều gì nó làm, ngoài việc gọi execve()hoặc _exit(), có một tiềm năng lớn để gây rối cho phụ huynh. Cụ thể, exit()gọi atexit()trình xử lý và "trình hoàn thiện" khác, ví dụ: nó xóa các luồng stdio. Trở về từ một vfork()đứa trẻ có khả năng (giống như trước đây) làm xáo trộn chồng của cha mẹ.
ninjalj

Tôi đã tự hỏi điều gì xảy ra với các chủ đề của quá trình cha mẹ; Có phải tất cả trong số họ được nhân bản hoặc chỉ chủ đề gọi là tòa nhà fork?
Mohammad Jafar Mashhadi

@LazerSharks vfork tạo ra một quy trình giống như luồng trong đó bộ nhớ được chia sẻ mà không có sự bảo vệ sao chép khi ghi, do đó, việc thực hiện ngăn xếp có thể làm hỏng quá trình cha.
Jasen

Câu trả lời:


159
  • vfork()là một tối ưu hóa lỗi thời. Trước khi quản lý bộ nhớ tốt, hãy fork()tạo một bản sao đầy đủ cho bộ nhớ của cha mẹ, vì vậy nó khá tốn kém. vì trong nhiều trường hợp, a fork()đã được theo sau exec(), loại bỏ bản đồ bộ nhớ hiện tại và tạo một bản đồ mới, đó là một chi phí không cần thiết. Ngày nay, fork()không sao chép bộ nhớ; nó chỉ đơn giản được đặt là "sao chép trên ghi", vì vậy fork()+ exec()cũng hiệu quả như vfork()+ exec().

  • clone()là tòa nhà được sử dụng bởi fork(). với một số tham số, nó tạo ra một tiến trình mới, với các tham số khác, nó tạo ra một luồng. sự khác biệt giữa chúng chỉ là cấu trúc dữ liệu (không gian bộ nhớ, trạng thái bộ xử lý, ngăn xếp, PID, tệp mở, v.v.) có được chia sẻ hay không.



22
vforktránh sự cần thiết phải tạm thời tạo ra nhiều bộ nhớ hơn để người ta có thể thực thi execvà nó vẫn hiệu quả hơn fork, ngay cả khi mức độ gần như không cao. Do đó, người ta có thể tránh việc phải sử dụng quá nhiều bộ nhớ chỉ để một chương trình lớn có thể tạo ra quá trình con. Vì vậy, không chỉ là tăng hiệu suất, mà còn có thể làm cho nó khả thi.
Ded repeatator

5
Trên thực tế, tôi đã chứng kiến ​​tận mắt cách fork () khác xa với giá rẻ khi RSS của bạn lớn. Tôi đoán điều này là do kernel vẫn phải sao chép tất cả các bảng trang.
Martina Ferrari

4
Nó phải sao chép tất cả các bảng trang, đặt tất cả các bản sao ghi vào bộ nhớ có thể ghi trong cả hai quy trình , xóa TLB và sau đó phải hoàn nguyên tất cả các thay đổi cho cha mẹ (và xóa lại TLB) exec.
zwol

3
vfork vẫn hữu ích trong cygwin (một dll giả lập kernel, chạy trên Windows của Microsoft). Cygwin không thể thực hiện một ngã ba hiệu quả, vì HĐH cơ bản không có.
ctrl-alt-delor

80
  • execve() thay thế hình ảnh thực thi hiện tại bằng một hình ảnh khác được tải từ một tập tin thực thi.
  • fork() tạo ra một quá trình con.
  • vfork()là một phiên bản tối ưu hóa lịch sử của fork(), có nghĩa là được sử dụng khi execve()được gọi trực tiếp sau đó fork(). Hóa ra nó hoạt động tốt trong các hệ thống không phải MMU (nơi fork()không thể hoạt động hiệu quả) và khi fork()xử lý với một bộ nhớ lớn để chạy một số chương trình nhỏ (nghĩ là Java Runtime.exec()). POSIX đã tiêu chuẩn hóa posix_spawn()để thay thế hai cách sử dụng hiện đại hơn nàyvfork() .
  • posix_spawn()không tương đương với a fork()/execve(), và cũng cho phép một số fd tung hứng ở giữa. Nó được cho là để thay thếfork()/execve() , chủ yếu cho các nền tảng không phải MMU.
  • pthread_create() tạo ra một chủ đề mới.
  • clone()là một cuộc gọi dành riêng cho Linux, có thể được sử dụng để thực hiện mọi thứ từ fork()đến pthread_create(). Nó cho rất nhiều sự kiểm soát. Lấy cảm hứng từrfork() .
  • rfork()là một cuộc gọi cụ thể Plan-9. Đây được coi là một cuộc gọi chung, cho phép chia sẻ nhiều mức độ, giữa các quy trình và chuỗi đầy đủ.

2
Cảm ơn vì đã thêm nhiều thông tin hơn những gì thực sự được yêu cầu, nó giúp tôi tiết kiệm thời gian của mình
Neeraj

5
Kế hoạch 9 là một trêu chọc như vậy.
JJ

1
Đối với những người không thể nhớ MMU có nghĩa là gì: "Đơn vị quản lý bộ nhớ" - đọc thêm trên Wikipedia
mgarey

43
  1. fork()- tạo ra một quy trình con mới, đó là một bản sao hoàn chỉnh của quy trình cha. Các tiến trình con và cha mẹ sử dụng các không gian địa chỉ ảo khác nhau, ban đầu được điền bởi cùng các trang bộ nhớ. Sau đó, khi cả hai quá trình được thực thi, các không gian địa chỉ ảo bắt đầu khác nhau ngày càng nhiều, bởi vì hệ điều hành thực hiện sao chép lười biếng các trang bộ nhớ được viết bởi một trong hai quy trình này và gán một bản sao độc lập của các trang đã sửa đổi của bộ nhớ cho mỗi quá trình. Kỹ thuật này được gọi là Copy-On-Write (COW).
  2. vfork()- tạo ra một quy trình con mới, đó là bản sao "nhanh" của quy trình cha. Ngược lại với cuộc gọi hệ thống fork(), các tiến trình con và cha mẹ chia sẻ cùng một không gian địa chỉ ảo. GHI CHÚ! Sử dụng cùng một không gian địa chỉ ảo, cả cha và con đều sử dụng cùng một ngăn xếp, con trỏ ngăn xếp và con trỏ lệnh, như trong trường hợp cổ điển fork()! Để ngăn chặn sự can thiệp không mong muốn giữa cha mẹ và con cái sử dụng cùng một ngăn xếp, việc thực thi quy trình cha mẹ được đóng băng cho đến khi đứa trẻ sẽ gọi exec()(tạo một không gian địa chỉ ảo mới và chuyển sang ngăn xếp khác) hoặc _exit()(chấm dứt thực hiện quy trình ). vfork()là tối ưu hóa fork()cho mô hình "fork-and-exec". Nó có thể được thực hiện nhanh hơn 4-5 lần so với fork(), bởi vì không giống nhưfork()(ngay cả khi ghi nhớ COW), việc thực hiện vfork()cuộc gọi hệ thống không bao gồm việc tạo không gian địa chỉ mới (phân bổ và thiết lập các thư mục trang mới).
  3. clone()- tạo ra một quá trình con mới. Các tham số khác nhau của cuộc gọi hệ thống này, chỉ định phần nào của quy trình cha phải được sao chép vào quy trình con và phần nào sẽ được chia sẻ giữa chúng. Kết quả là, cuộc gọi hệ thống này có thể được sử dụng để tạo ra tất cả các loại thực thể thực thi, bắt đầu từ các luồng và hoàn thiện bởi các quy trình hoàn toàn độc lập. Trong thực tế, clone()cuộc gọi hệ thống là cơ sở được sử dụng để thực hiện pthread_create()và tất cả các họ của fork()hệ thống gọi.
  4. exec()- đặt lại tất cả bộ nhớ của quá trình, tải và phân tích nhị phân thực thi được chỉ định, thiết lập ngăn xếp mới và chuyển điều khiển đến điểm vào của tệp thực thi được tải. Cuộc gọi hệ thống này không bao giờ trả lại quyền điều khiển cho người gọi và phục vụ cho việc tải một chương trình mới vào quy trình đã tồn tại. Cuộc gọi hệ thống này với cuộc gọi fork()hệ thống cùng nhau tạo thành một mô hình quản lý quy trình UNIX cổ điển được gọi là "fork-and-exec".

2
Lưu ý rằng các yêu cầu BSD và POSIX vforkyếu đến mức sẽ là hợp pháp khi tạo vforkmột từ đồng nghĩa của fork(và POSIX.1-2008 loại bỏ vforkhoàn toàn khỏi thông số kỹ thuật). Nếu bạn tình cờ kiểm tra mã của mình trên một hệ thống đồng nghĩa với chúng (ví dụ: hầu hết các BSD sau 4.4 ngoài NetBSD, hạt nhân Linux trước 2.2.0-pre6, v.v.), nó có thể hoạt động ngay cả khi bạn vi phạm vforkhợp đồng, sau đó phát nổ nếu bạn chạy nó ở nơi khác Một số trong những mô phỏng nó với fork(ví dụ OpenBSD) vẫn đảm bảo cha mẹ không tiếp tục chạy cho đến khi con exechoặc _exits. Nó vô lý không di động.
ShadowRanger

2
liên quan đến câu cuối cùng của điểm thứ 3 của bạn: Tôi nhận thấy trên Linux bằng cách sử dụng strace rằng trong khi thực sự trình bao bọc glibc cho fork () gọi tòa nhà nhân bản, trình bao bọc cho vfork () gọi tòa nhà vfork
ilstam

7

Tất cả fork (), vfork () và clone () đều gọi do_fork () để thực hiện công việc thực tế, nhưng với các tham số khác nhau.

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, &regs, 0);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
}
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_VM    0x00000100  /* set if VM shared between processes */

SIGCHLD means the child should send this signal to its father when exit.

Đối với fork, đứa trẻ và cha có bảng trang VM độc lập, nhưng vì tính hiệu quả, fork sẽ không thực sự sao chép bất kỳ trang nào, nó chỉ đặt tất cả các trang có thể ghi thành chỉ đọc cho quá trình con. Vì vậy, khi tiến trình con muốn viết một cái gì đó trên trang đó, một ngoại lệ trang xảy ra và kernel sẽ phân bổ một trang mới được sao chép từ trang cũ với quyền viết. Đó gọi là "bản sao trên văn bản".

Đối với vfork, bộ nhớ ảo chính xác là do con và cha --- chỉ vì điều đó, cha và con không thể tỉnh táo đồng thời vì chúng sẽ ảnh hưởng lẫn nhau. Vì vậy, người cha sẽ ngủ ở cuối "do_fork ()" và thức dậy khi con gọi exit () hoặc execve () kể từ đó nó sẽ sở hữu bảng trang mới. Đây là đoạn mã (in do_fork ()) mà người cha ngủ.

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;

Đây là đoạn mã (tính bằng mm_release () được gọi bởi exit () và execve ()) đánh thức người cha.

up(tsk->p_opptr->vfork_sem);

Đối với sys_clone (), nó linh hoạt hơn vì bạn có thể nhập bất kỳ clone_flags nào vào nó. Vì vậy, pthread_create () gọi cuộc gọi hệ thống này với nhiều clone_flags:

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM);

Tóm tắt: fork (), vfork () và clone () sẽ tạo các tiến trình con với các tài nguyên chia sẻ khác nhau với tiến trình cha. Chúng ta cũng có thể nói vfork () và clone () có thể tạo các luồng (thực ra chúng là các tiến trình vì chúng có task_struct độc lập) vì chúng chia sẻ bảng trang VM với tiến trình cha.


-4

trong fork (), tiến trình con hoặc cha mẹ sẽ thực thi dựa trên lựa chọn cpu .. Nhưng trong vfork (), chắc chắn con sẽ thực thi trước. Sau khi con chấm dứt, cha mẹ sẽ thực thi.


3
Sai lầm. vfork()chỉ có thể được thực hiện như fork().
ninjalj

sau AnyFork (), không xác định ai sẽ chạy cha mẹ / con đầu tiên.
AjayKumarBasuthkar

5
@Raj: Bạn có một số hiểu lầm về khái niệm nếu bạn nghĩ sau khi giả mạo có một khái niệm ngầm về trật tự nối tiếp. Forking tạo ra một quy trình mới và sau đó trả lại quyền điều khiển cho cả hai quy trình (mỗi quy trình trả về một khác nhau pid) - hệ điều hành có thể lên lịch cho quy trình mới để chạy song song nếu điều đó có ý nghĩa (ví dụ: nhiều bộ xử lý). Nếu vì một lý do nào đó, bạn cần các quy trình này để thực hiện theo một thứ tự nối tiếp cụ thể, thì bạn cần đồng bộ hóa bổ sung mà việc cung cấp không cung cấp; thành thật mà nói, bạn có thể thậm chí không muốn một ngã ba ở nơi đầu tiên.
Andon M. Coleman

Thật ra @AjayKumarBasuthkar và @ninjalj, bạn đều sai. với vfork(), đứa trẻ chạy trước. Đó là trong các trang người đàn ông; hành quyết của cha mẹ bị đình chỉ cho đến khi đứa trẻ chết hoặc execs. Và ninjalj tra cứu mã nguồn kernel. Không có cách nào để thực hiện vfork()như fork()vì họ vượt qua đối số khác nhau để do_fork()trong kernel. Tuy nhiên, bạn có thể triển khai vforkvới tòa nhà clonechọc trời
Zac Wimer

@ZacWimer: xem bình luận của ShadowRanger cho câu trả lời khác stackoverflow.com/questions/4856255/ trộm Old Linux đã đồng bộ hóa chúng, như các BSD khác với NetBSD (có xu hướng được chuyển sang nhiều hệ thống không phải MMU). Từ trang web Linux: Trong 4.4BSD, nó được tạo ra đồng nghĩa với fork (2) nhưng NetBSD đã giới thiệu lại; xem ⟨netbsd.org/Documentation/kernel/vfork.html . Trong Linux, nó tương đương với fork (2) cho đến 2.2.0-pre6 hoặc hơn.
ninjalj
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.