Làm thế nào để các máy chủ web nghe các địa chỉ IP, làm gián đoạn hoặc bỏ phiếu?


87

Tôi đang cố gắng để hiểu các chi tiết thấp hơn của các máy chủ web. Tôi tự hỏi nếu một máy chủ, nói Apache, liên tục bỏ phiếu cho các yêu cầu mới hoặc Nếu nó hoạt động bởi một loại hệ thống ngắt. Nếu đó là một ngắt, điều gì gây ra sự gián đoạn, đó có phải là trình điều khiển card mạng không?


1
Từ khóa cần hiểu là "máy chủ" . Trong mô hình máy chủ-máy khách (so với mô hình chủ-nô), máy chủ chờ các yêu cầu từ máy khách. Những yêu cầu này là các sự kiện cần được phục vụ. Một máy chủ web là một chương trình ứng dụng. Câu hỏi của bạn kết hợp SW ứng dụng với thuật ngữ CTNH (ví dụ: ngắt và NIC), thay vì giữ các khái niệm liên quan ở cùng một lớp trừu tượng. Trình điều khiển NIC đôi khi thực sự có thể sử dụng bỏ phiếu, ví dụ trình điều khiển Linux NAPI thoái lui để bỏ phiếu khi có lũ gói. Nhưng điều đó không liên quan đến ứng dụng xử lý sự kiện SW.
mùn cưa

1
@sawdust Rất thú vị. Câu hỏi thực sự có nghĩa là để hiểu mối liên hệ giữa các quy trình SW và CTNH
user2202911

1
Nó rất giống với cách các chương trình dòng lệnh (và GUI khác) nghe bàn phím. Đặc biệt là trong một hệ thống cửa sổ, nơi bạn có bước nhân nhận dữ liệu từ thiết bị bàn phím và đưa nó cho trình quản lý cửa sổ, xác định cửa sổ có tiêu điểm và đưa dữ liệu vào cửa sổ đó.
G-Man

@ G-Man: Tôi lý thuyết, vâng. Trong thực tế, hầu hết những người đánh máy không gõ với tốc độ 1 Gbit / s, điều này biện minh cho việc có hai kiến ​​trúc khác nhau. Một sạch sẽ, linh hoạt và chậm, một vụng về nhưng tốc độ cao.
MSalters

Câu trả lời:


181

Câu trả lời ngắn gọn là: một số loại hệ thống ngắt. Về cơ bản, họ sử dụng chặn I / O, nghĩa là họ ngủ (chặn) trong khi chờ dữ liệu mới.

  1. Máy chủ tạo ra một ổ cắm nghe và sau đó chặn trong khi chờ kết nối mới. Trong thời gian này, kernel đưa tiến trình vào trạng thái ngủ gián đoạn và chạy các tiến trình khác. Đây là một điểm quan trọng: việc thăm dò quá trình liên tục sẽ làm lãng phí CPU. Nhân có thể sử dụng tài nguyên hệ thống hiệu quả hơn bằng cách chặn quá trình cho đến khi có công việc để làm.

  2. Khi dữ liệu mới đến trên mạng, card mạng sẽ bị gián đoạn.

  3. Thấy rằng có một ngắt từ card mạng, kernel, thông qua trình điều khiển card mạng, đọc dữ liệu mới từ card mạng và lưu nó vào bộ nhớ. (Điều này phải được thực hiện nhanh chóng và thường được xử lý bên trong trình xử lý ngắt.)

  4. Nhân xử lý dữ liệu mới đến và liên kết nó với một ổ cắm. Một quá trình đang chặn trên ổ cắm đó sẽ được đánh dấu có thể chạy được, có nghĩa là nó hiện đủ điều kiện để chạy. Nó không nhất thiết phải chạy ngay lập tức (kernel có thể quyết định chạy các tiến trình khác).

  5. Khi rảnh rỗi, kernel sẽ đánh thức quá trình máy chủ web bị chặn. (Vì bây giờ nó có thể chạy được.)

  6. Quá trình máy chủ web tiếp tục thực thi như thể không có thời gian trôi qua. Hệ thống chặn cuộc gọi của nó trả về và nó xử lý bất kỳ dữ liệu mới nào. Sau đó ... chuyển sang bước 1.


18
+1 để phân định rõ ràng kernel so với quy trình máy chủ web.
Russell Borogove

13
Tôi không thể tin một cái gì đó phức tạp vì điều này có thể được tóm tắt rất rõ ràng và đơn giản, nhưng bạn đã làm nó. +1
Brandon

8
+1 Câu trả lời tuyệt vời. Ngoài ra, các bước giữa 2 và 3 có thể phức tạp hơn một chút với các trình điều khiển, hệ điều hành và trình điều khiển hiện đại. Ví dụ, với NAPI trên Linux, các gói không thực sự nhận được trong bối cảnh gián đoạn. Thay vào đó, kernel nói "Được rồi, tôi hiểu là bạn đã có dữ liệu. Thoát khỏi lỗi (vô hiệu hóa nguồn ngắt) và tôi sẽ quay lại ngay để lấy gói này bất kỳ gói tiếp theo nào có thể đến trước khi tôi làm."
Jonathon Reinhart

8
Nhẹ nitpick: Không thực sự cần thiết để chặn. Ngay khi quá trình máy chủ tạo ra một ổ cắm nghe, kernel sẽ chấp nhận các SYN trên cổng đó, ngay cả khi bạn không bị chặn bên trong accept. Chúng (may mắn thay, hoặc nó hoàn toàn hút!) Các tác vụ chạy không đồng bộ, độc lập. Khi các kết nối đến, chúng được đặt vào một hàng đợi nơi acceptchúng kéo chúng ra. Chỉ khi không có, nó chặn.
Damon

3
"đọc dữ liệu mới từ card mạng và lưu trữ vào bộ nhớ. (Việc này phải được thực hiện nhanh chóng và thường được xử lý bên trong trình xử lý ngắt." "Không phải nó được thực hiện với truy cập bộ nhớ trực tiếp sao?
Siyuan Ren

9

Có khá nhiều chi tiết "thấp hơn".

Đầu tiên, hãy xem xét rằng kernel có một danh sách các quy trình và tại bất kỳ thời điểm nào, một số quy trình này đang chạy và một số thì không. Nhân cho phép mỗi tiến trình chạy một vài lát thời gian CPU, sau đó ngắt nó và chuyển sang phần tiếp theo. Nếu không có các tiến trình có thể chạy được, thì hạt nhân có thể sẽ đưa ra một lệnh như HLT cho CPU tạm dừng CPU cho đến khi có sự gián đoạn phần cứng.

Đâu đó trong máy chủ là một cuộc gọi hệ thống có nội dung "hãy cho tôi một việc phải làm". Có hai loại cách thức này có thể được thực hiện. Trong trường hợp của Apache, nó gọi acceptvào một ổ cắm mà Apache đã mở trước đó, có thể đang nghe trên cổng 80. Hạt nhân duy trì một hàng đợi các nỗ lực kết nối và thêm vào hàng đợi đó mỗi khi nhận được TCP TCP . Làm thế nào hạt nhân biết TCP TCP được nhận tùy thuộc vào trình điều khiển thiết bị; đối với nhiều NIC có thể bị gián đoạn phần cứng khi nhận được dữ liệu mạng.

acceptyêu cầu kernel trả lại cho tôi sự khởi đầu kết nối tiếp theo. Nếu hàng đợi không trống, thì acceptchỉ cần trả về ngay lập tức. Nếu hàng đợi trống, thì quy trình (Apache) sẽ bị xóa khỏi danh sách các quy trình đang chạy. Khi một kết nối được bắt đầu sau đó, quá trình được nối lại. Điều này được gọi là "chặn", bởi vì trong quá trình gọi nó, có accept()vẻ như một hàm không quay trở lại cho đến khi nó có kết quả, có thể là một thời gian kể từ bây giờ. Trong thời gian đó quá trình không thể làm gì khác.

Khi accepttrở về, Apache biết rằng ai đó đang cố gắng bắt đầu một kết nối. Sau đó, nó gọi fork để phân chia quy trình Apache thành hai quy trình giống hệt nhau. Một trong những quy trình này tiếp tục xử lý yêu cầu HTTP, các cuộc gọi khác acceptlại nhận được kết nối tiếp theo. Do đó, luôn có một quy trình chính không có gì ngoài việc gọi acceptvà sinh ra các quy trình con, và sau đó sẽ có một quy trình phụ cho mỗi yêu cầu.

Đây là một sự đơn giản hóa: có thể thực hiện việc này bằng các luồng thay vì các quy trình và cũng có thể thực hiện forktrước để có một quy trình công nhân sẵn sàng thực hiện khi nhận được yêu cầu, do đó giảm chi phí khởi động. Tùy thuộc vào cách Apache được cấu hình, nó có thể thực hiện một trong những điều này.

Đó là loại rộng đầu tiên như thế nào để làm điều đó, và nó được gọi là chặn IO vì hệ thống gọi như acceptreadwritehoạt động trên socket sẽ ngừng quá trình này cho đến khi họ có một cái gì đó để quay trở lại.

Cách rộng khác để làm điều đó được gọi là IO không chặn hoặc dựa trên sự kiện hoặc IO không đồng bộ . Điều này được thực hiện với các cuộc gọi hệ thống như selecthoặc epoll. Mỗi cái đều làm điều tương tự: bạn cung cấp cho chúng một danh sách các socket (hoặc nói chung, mô tả tệp) và những gì bạn muốn làm với chúng, và các khối kernel cho đến khi nó sẵn sàng thực hiện một trong những điều đó.

Với mô hình này, bạn có thể nói với kernel (với epoll), "Hãy cho tôi biết khi nào có kết nối mới trên cổng 80 hoặc dữ liệu mới để đọc trên bất kỳ 9471 kết nối nào khác mà tôi đã mở". epollchặn cho đến khi một trong những điều đó sẵn sàng, sau đó bạn làm điều đó. Sau đó, bạn lặp lại. Hệ thống gọi như acceptreadwritekhông bao giờ khối, một phần vì bất cứ khi nào bạn gọi cho họ, epollchỉ nói với bạn rằng họ đã sẵn sàng để thì sẽ không có lý do gì để ngăn chặn, và cũng bởi vì khi bạn mở ổ cắm hoặc các tập tin bạn chỉ định rằng bạn muốn họ trong chế độ không chặn, vì vậy những cuộc gọi đó sẽ thất bại EWOULDBLOCKthay vì chặn.

Ưu điểm của mô hình này là bạn chỉ cần một quy trình. Điều này có nghĩa là bạn không phải phân bổ cấu trúc ngăn xếp và kernel cho mỗi yêu cầu. NginxHAProxy sử dụng mô hình này và đó là lý do lớn để họ có thể xử lý nhiều kết nối hơn Apache trên phần cứng tương tự.

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.