Vietnamese (machine translation)

Lưu ý

Mục đích của file này là để độc giả tiếng Việt có thể đọc và hiểu tài liệu nhân kernel dễ dàng hơn, không phải để tạo ra một nhánh tài liệu riêng. Nếu bạn có bất kỳ nhận xét hoặc cập nhật nào cho file này, vui lòng thử cập nhật file tiếng Anh gốc trước. Nếu bạn thấy có sự khác biệt giữa bản dịch và bản gốc, hoặc có vấn đề về bản dịch, vui lòng gửi góp ý hoặc patch cho người dịch của file này, hoặc nhờ người bảo trì và người review tài liệu tiếng Việt giúp đỡ.

Bản gốc:

Unreliable Guide To Hacking The Linux Kernel

Người dịch:

Google Translate (machine translation)

Phiên bản gốc:

8541d8f725c6

Cảnh báo

Tài liệu này được dịch tự động bằng máy và chưa được review bởi người dịch. Nội dung có thể không chính xác hoặc khó hiểu ở một số chỗ. Khi có sự khác biệt với bản gốc, bản gốc luôn là chuẩn. Bản dịch chất lượng cao (được review) được đặt trong thư mục vi_VN/.

Hướng dẫn không đáng tin cậy để hack hạt nhân Linux

Tác giả:

Rusty Russell

Giới thiệu

Chào mừng độc giả thân mến đến với Hướng dẫn về Linux không đáng tin cậy đáng chú ý của Rusty Hack hạt nhân. Tài liệu này mô tả các hoạt động thông thường và các yêu cầu đối với mã hạt nhân: mục tiêu của nó là dùng làm mồi cho Linux phát triển kernel cho các lập trình viên C có kinh nghiệm. Tôi tránh thực hiện chi tiết: đó là mục đích của mã và tôi bỏ qua toàn bộ phần của thói quen hữu ích.

Trước khi bạn đọc điều này, hãy hiểu rằng tôi không bao giờ muốn viết tài liệu này, hoàn toàn không đủ tiêu chuẩn, nhưng tôi luôn muốn đọc nó, và đây là cách duy nhất. Tôi hy vọng nó sẽ phát triển thành một bản tóm tắt các phương pháp hay nhất, điểm khởi đầu chung và ngẫu nhiên thông tin.

các cầu thủ

Tại bất kỳ thời điểm nào, mỗi CPU trong hệ thống có thể:

  • không liên quan đến bất kỳ quy trình nào, phục vụ ngắt phần cứng;

  • không liên quan đến bất kỳ quy trình nào, phục vụ phần mềm hoặc tasklet;

  • chạy trong không gian kernel, được liên kết với một tiến trình (ngữ cảnh người dùng);

  • chạy một tiến trình trong không gian người dùng.

Có một thứ tự giữa những điều này. Hai người dưới cùng có thể giành quyền trước khác, nhưng trên đó là một hệ thống phân cấp chặt chẽ: mỗi cái chỉ có thể được ưu tiên bởi những cái ở trên nó. Ví dụ: trong khi softirq đang chạy trên CPU, không có softirq nào khác có thể sử dụng trước nó, nhưng một phần cứng có thể làm gián đoạn. Tuy nhiên, bất kỳ CPU nào khác trong hệ thống đều thực thi độc lập.

Chúng ta sẽ thấy một số cách mà ngữ cảnh người dùng có thể chặn các ngắt, trở nên thực sự không thể đánh trước được.

Bối cảnh người dùng

Bối cảnh của người dùng là khi bạn đến từ một cuộc gọi hệ thống hoặc cái bẫy khác: giống như không gian người dùng, bạn có thể được ưu tiên thực hiện các nhiệm vụ quan trọng hơn và ngắt quãng. Bạn có thể ngủ bằng cách gọi lịch trình().

Lưu ý

You are always in user context on module load and unload, and on operations on the block device layer.

Trong ngữ cảnh người dùng, con trỏ ZZ0000ZZ (cho biết nhiệm vụ chúng ta đang thực hiện hiện đang thực thi) là hợp lệ và in_interrupt() (ZZ0001ZZ) là sai.

Cảnh báo

Beware that if you have preemption or softirqs disabled (see below), in_interrupt() will return a false positive.

Ngắt phần cứng (IRQ cứng)

Tích tắc hẹn giờ, card mạng và bàn phím là những ví dụ về phần cứng thực tạo ra sự gián đoạn bất cứ lúc nào. Kernel chạy ngắt trình xử lý, phục vụ phần cứng. Hạt nhân đảm bảo rằng điều này trình xử lý không bao giờ được nhập lại: nếu cùng một ngắt đến, nó sẽ được xếp hàng đợi (hoặc bị đánh rơi). Vì nó vô hiệu hóa các ngắt nên trình xử lý này phải được nhanh: thường thì nó chỉ đơn giản là xác nhận sự gián đoạn, đánh dấu một ‘phần mềm ngắt’ để thực thi và thoát.

Bạn có thể biết bạn đang bị gián đoạn phần cứng, vì in_hardirq() trả về đúng.

Cảnh báo

Beware that this will return a false positive if interrupts are disabled (see below).

Bối cảnh ngắt phần mềm: Softirqs và Tasklets

Bất cứ khi nào một cuộc gọi hệ thống sắp quay trở lại không gian người dùng hoặc phần cứng thoát trình xử lý ngắt, mọi ‘ngắt phần mềm’ được đánh dấu đang chờ xử lý (thường là do ngắt phần cứng) được chạy (ZZ0000ZZ).

Phần lớn công việc xử lý ngắt thực sự được thực hiện ở đây. Sớm trong chuyển sang SMP, chỉ có ‘nửa dưới’ (BH), không có tận dụng nhiều CPU. Ngay sau khi chúng tôi chuyển từ chế độ gió lên máy tính làm từ que diêm và nước mũi, chúng ta đã từ bỏ giới hạn này và chuyển sang ‘softirqs’.

ZZ0000ZZ liệt kê các phần mềm khác nhau. Rất softirq quan trọng là softirq hẹn giờ (ZZ0001ZZ): bạn có thể đăng ký để nó gọi các chức năng cho bạn trong khoảng thời gian nhất định thời gian.

Softirq thường khó xử lý vì cùng một softirq sẽ chạy đồng thời trên nhiều CPU. Vì lý do này, các tasklet (ZZ0000ZZ) thường được sử dụng nhiều hơn: chúng là có thể đăng ký động (có nghĩa là bạn có thể có bao nhiêu tùy thích) và họ cũng đảm bảo rằng bất kỳ tasklet nào cũng sẽ chỉ chạy trên một CPU bất kỳ lúc nào. thời gian, mặc dù các tác vụ khác nhau có thể chạy đồng thời.

Cảnh báo

The name ‘tasklet’ is misleading: they have nothing to do with ‘tasks’.

Bạn có thể biết mình đang ở trong softirq (hoặc tasklet) bằng cách sử dụng macro in_softirq() (ZZ0000ZZ).

Cảnh báo

Beware that this will return a false positive if a bottom half lock is held.

Một số quy tắc cơ bản

Không bảo vệ bộ nhớ

Nếu bạn làm hỏng bộ nhớ, cho dù trong bối cảnh người dùng hay bối cảnh gián đoạn, toàn bộ máy sẽ bị hỏng. Bạn có chắc là bạn không thể làm được điều bạn muốn trong không gian người dùng?

Không có dấu phẩy động hoặc MMX

Bối cảnh FPU không được lưu; ngay cả trong bối cảnh người dùng, trạng thái FPU có thể sẽ không tương ứng với quy trình hiện tại: bạn sẽ làm rối tung với trạng thái FPU của một số quy trình người dùng. Nếu bạn thực sự muốn làm điều này, bạn sẽ phải lưu/khôi phục đầy đủ trạng thái FPU một cách rõ ràng (và tránh chuyển đổi ngữ cảnh). Nói chung đó là một ý tưởng tồi; sử dụng điểm cố định số học đầu tiên.

Giới hạn ngăn xếp cứng nhắc

Tùy thuộc vào các tùy chọn cấu hình, ngăn xếp hạt nhân có dung lượng khoảng 3K đến 6K cho hầu hết kiến trúc 32-bit: khoảng 14K trên hầu hết kiến trúc 64-bit vòm và thường được chia sẻ với các ngắt nên bạn không thể sử dụng hết. Tránh đệ quy sâu và mảng cục bộ lớn trên ngăn xếp (phân bổ thay vào đó chúng sẽ động).

Nhân Linux có thể mang theo được

Hãy giữ nó như vậy. Mã của bạn phải sạch 64-bit và độc lập với endian. Bạn cũng nên giảm thiểu những nội dung cụ thể của CPU, ví dụ: lắp ráp nội tuyến phải được đóng gói sạch sẽ và giảm thiểu đến mức chuyển cổng dễ dàng. Nói chung nó nên được giới hạn ở phần phụ thuộc vào kiến trúc của cây hạt nhân.

ioctls: Không viết cuộc gọi hệ thống mới

Một cuộc gọi hệ thống thường trông như thế này:

asmlinkage dài sys_mycall(int arg)
{

trả về 0;

}

Đầu tiên, trong hầu hết các trường hợp, bạn không muốn tạo cuộc gọi hệ thống mới. bạn tạo một thiết bị ký tự và triển khai ioctl thích hợp cho nó. Điều này linh hoạt hơn nhiều so với các cuộc gọi hệ thống, không cần phải nhập trong ZZ0000ZZ của mọi kiến trúc và ZZ0001ZZ và có nhiều khả năng được chấp nhận hơn Linus.

Nếu tất cả công việc thường ngày của bạn là đọc hoặc ghi một số tham số, hãy xem xét thay vào đó, hãy triển khai giao diện sysfs().

Bên trong ioctl, bạn đang ở trong bối cảnh người dùng của một quy trình. Khi có lỗi xảy ra, bạn trả về một lỗi bị phủ định (xem ZZ0000ZZ, ZZ0001ZZ và ZZ0002ZZ), nếu không bạn trả về 0.

Sau khi ngủ bạn nên kiểm tra xem có tín hiệu nào xuất hiện không: Unix/Linux cách xử lý tín hiệu là tạm thời thoát khỏi cuộc gọi hệ thống bằng Lỗi ZZ0000ZZ. Mã nhập cuộc gọi hệ thống sẽ chuyển về ngữ cảnh người dùng, xử lý bộ xử lý tín hiệu và sau đó lệnh gọi hệ thống của bạn sẽ được khởi động lại (trừ khi người dùng tắt tính năng đó). Vì vậy bạn nên chuẩn bị để xử lý việc khởi động lại, ví dụ: nếu bạn đang trong quá trình thao túng một số cấu trúc dữ liệu.

if (signal_pending(current))

trả về -ERESTARTSYS;

Nếu bạn đang thực hiện các phép tính dài hơn: trước tiên hãy nghĩ đến không gian người dùng. Nếu bạn ZZ0000ZZ muốn làm điều đó trong kernel bạn nên thường xuyên kiểm tra nếu cần từ bỏ CPU (hãy nhớ rằng CPU có tính năng đa nhiệm hợp tác). Thành ngữ:

cond_resched(); /Sẽ ngủ/

Một lưu ý ngắn về thiết kế giao diện: phương châm gọi hệ thống UNIX là “Cung cấp cơ chế chứ không phải chính sách”.

Bí quyết cho sự bế tắc

Bạn không thể gọi bất kỳ quy trình nào có thể ngủ, trừ khi:

  • Bạn đang ở trong bối cảnh người dùng.

  • Bạn không sở hữu bất kỳ spinlock nào.

  • Bạn đã kích hoạt tính năng ngắt (thực ra, Andi Kleen nói rằng

    mã lập kế hoạch sẽ kích hoạt chúng cho bạn, nhưng điều đó có thể không những gì bạn muốn).

Lưu ý rằng một số chức năng có thể ngủ ngầm: những chức năng phổ biến là người dùng hàm truy cập không gian (*_user) và hàm cấp phát bộ nhớ không có ZZ0000ZZ.

Bạn phải luôn biên dịch kernel ZZ0000ZZ của mình trên, và nó sẽ cảnh báo bạn nếu bạn vi phạm các quy tắc này. Nếu bạn ZZ0001ZZ phá vỡ quy tắc, cuối cùng bạn sẽ khóa hộp của bạn.

Thật sự.

Thói quen chung

printk()

Được xác định trong ZZ0000ZZ

printk() cung cấp các thông báo kernel tới bàn điều khiển, dmesg và daemon nhật ký hệ thống. Nó rất hữu ích cho việc gỡ lỗi và báo cáo lỗi, và có thể được sử dụng bên trong ngữ cảnh ngắt, nhưng hãy thận trọng khi sử dụng: một cái máy bảng điều khiển chứa đầy thông báo printk không thể sử dụng được. Nó sử dụng một chuỗi định dạng hầu như tương thích với ANSI C printf và chuỗi C nối để cung cấp cho nó đối số “ưu tiên” đầu tiên:

printk(KERN_INFO “i = %un”, i);

Xem ZZ0000ZZ; đối với các giá trị ZZ0001ZZ khác; đây là được syslog hiểu là cấp độ. Trường hợp đặc biệt: để in IP sử dụng địa chỉ:

__be32 ipaddress;

printk(KERN_INFO “ip của tôi: %pI4n”, &ipaddress);

printk() sử dụng bộ đệm 1K bên trong và không bắt được vượt quá. Hãy chắc chắn rằng điều đó sẽ là đủ.

Lưu ý

You will know when you are a real kernel hacker when you start typoing printf as printk in your user programs :)

Lưu ý

Another sidenote: the original Unix Version 6 sources had a comment on top of its printf function: “Printf should not be used for chit-chat”. You should follow that advice.

copy_to_user() / copy_from_user() / get_user() / put_user()

Được xác định trong ZZ0000ZZ / ZZ0001ZZ

ZZ0000ZZ

put_user()get_user() được sử dụng để lấy và đặt các giá trị đơn lẻ (chẳng hạn như int, char hoặc long) từ và tới không gian người dùng. Một con trỏ vào không gian người dùng không bao giờ được hủy đăng ký đơn giản: dữ liệu nên được sao chép bằng cách sử dụng các thói quen này. Cả hai đều trả về ZZ0000ZZ hoặc 0.

copy_to_user()copy_from_user() là tổng quát hơn: họ sao chép một lượng dữ liệu tùy ý đến và đi từ không gian người dùng.

Cảnh báo

Unlike put_user() and get_user(), they return the amount of uncopied data (ie. 0 still means success).

[Vâng, giao diện khó chịu này làm tôi khó chịu. Cuộc chiến tranh lửa đến lên mỗi năm hoặc lâu hơn. --RR.]

Các chức năng có thể ngủ ngầm. Điều này không bao giờ nên được gọi bên ngoài bối cảnh người dùng (điều này vô nghĩa), với các ngắt bị vô hiệu hóa hoặc spinlock được giữ.

kmalloc()/kfree()

Được xác định trong ZZ0000ZZ

ZZ0000ZZ

Các thủ tục này được sử dụng để yêu cầu động các đoạn được căn chỉnh bằng con trỏ của bộ nhớ, như malloc và free trong không gian người dùng, nhưng kmalloc() nhận thêm một từ gắn cờ. Các giá trị quan trọng:

ZZ0000ZZ

Có thể ngủ và trao đổi để giải phóng bộ nhớ. Chỉ được phép trong ngữ cảnh của người dùng, nhưng là cách đáng tin cậy nhất để cấp phát bộ nhớ.

ZZ0000ZZ

Đừng ngủ. Ít tin cậy hơn ZZ0001ZZ, nhưng có thể được gọi là từ bối cảnh gián đoạn. Bạn nên có ZZ0002ZZ tốt chiến lược xử lý lỗi hết bộ nhớ.

ZZ0000ZZ

Phân bổ ISA DMA thấp hơn 16MB. Nếu bạn không biết đó là bạn không cần nó. Rất không đáng tin cậy.

Nếu bạn thấy chức năng ngủ được gọi từ cảnh báo ngữ cảnh không hợp lệ thì có thể bạn đã gọi hàm phân bổ chế độ ngủ từ bối cảnh ngắt mà không có ZZ0000ZZ. Bạn thực sự nên khắc phục điều đó. Chạy, đừng đi bộ.

Nếu bạn đang phân bổ ít nhất ZZ0000ZZ (ZZ0001ZZ hoặc ZZ0002ZZ), hãy cân nhắc sử dụng __get_free_pages() (ZZ0003ZZ). Nó nhận một đối số thứ tự (0 cho kích thước trang, 1 cho trang đôi, 2 cho bốn trang, v.v.) và cùng mức ưu tiên bộ nhớ cờ từ như trên.

Nếu bạn đang phân bổ nhiều hơn một byte có giá trị trang, bạn có thể sử dụng vmalloc(). Nó sẽ phân bổ bộ nhớ ảo trong kernel bản đồ. Khối này không liền kề trong bộ nhớ vật lý, nhưng MMU tạo ra có vẻ như nó dành cho bạn (vì vậy nó sẽ chỉ trông liền kề với CPU, không cho trình điều khiển thiết bị bên ngoài). Nếu bạn thực sự cần vật chất lớn bộ nhớ liền kề cho một số thiết bị lạ, bạn gặp vấn đề: đó là được hỗ trợ kém trong Linux vì sau một thời gian bị phân mảnh bộ nhớ trong kernel đang chạy làm cho nó khó khăn. Cách tốt nhất là phân bổ khối sớm trong quá trình khởi động thông qua alloc_bootmem() thường lệ.

Trước khi phát minh ra bộ nhớ đệm của riêng bạn cho các đối tượng thường được sử dụng, hãy cân nhắc việc sử dụng một bộ đệm phiến trong ZZ0000ZZ

hiện hành

Được xác định trong ZZ0000ZZ

Biến toàn cục này (thực sự là macro) chứa một con trỏ tới giá trị hiện tại cấu trúc tác vụ, do đó chỉ hợp lệ trong ngữ cảnh của người dùng. Ví dụ, khi một tiến trình thực hiện lời gọi hệ thống, điều này sẽ chỉ ra cấu trúc nhiệm vụ của quá trình gọi điện. Đó là ZZ0000ZZ trong bối cảnh ngắt.

mdelay()/udelay()

Được xác định trong ZZ0000ZZ / ZZ0001ZZ

Các hàm udelay()ndelay() có thể được sử dụng cho những khoảng dừng nhỏ. Không sử dụng các giá trị lớn với chúng vì bạn có nguy cơ tràn - hàm trợ giúp mdelay() rất hữu ích ở đây, hoặc hãy xem xét msleep().

cpu_to_be32()/be32_to_cpu()/cpu_to_le32()/le32_to_cpu()

Được xác định trong ZZ0000ZZ

Họ cpu_to_be32() (trong đó “32” có thể được thay thế bằng 64 hoặc 16, và “be” có thể được thay thế bằng “le”) là cách chung để thực hiện chuyển đổi endian trong kernel: chúng trả về giá trị được chuyển đổi. Tất cả các biến thể cũng cung cấp điều ngược lại: be32_to_cpu(), v.v.

Có hai biến thể chính của các hàm này: con trỏ biến thể, chẳng hạn như cpu_to_be32p(), lấy một con trỏ về loại đã cho và trả về giá trị được chuyển đổi. Biến thể khác là họ “tại chỗ”, chẳng hạn như cpu_to_be32s(), chuyển đổi giá trị được con trỏ tham chiếu và trả về khoảng trống.

local_irq_save()/local_irq_restore()

Được xác định trong ZZ0000ZZ

Các quy trình này vô hiệu hóa các ngắt cứng trên CPU cục bộ và khôi phục họ. Họ đang quay trở lại; lưu trạng thái trước đó vào trạng thái của họ Đối số ZZ0000ZZ. Nếu bạn biết rằng ngắt là được bật, bạn chỉ cần sử dụng local_irq_disable()local_irq_enable().

local_bh_disable()/local_bh_enable()

Được xác định trong ZZ0000ZZ

Các quy trình này vô hiệu hóa các ngắt mềm trên CPU cục bộ và khôi phục họ. Họ đang quay trở lại; nếu các ngắt mềm đã bị vô hiệu hóa trước đó, chúng vẫn sẽ bị vô hiệu hóa sau khi cặp chức năng này được gọi. Chúng ngăn không cho softirq và tasklets chạy trên CPU hiện tại.

smp_processor_id()

Được xác định trong ZZ0000ZZ

get_cpu() vô hiệu hóa quyền ưu tiên (vì vậy bạn sẽ không đột nhiên nhận được được chuyển sang CPU khác) và trả về số bộ xử lý hiện tại, trong khoảng từ 0 và ZZ0000ZZ. Lưu ý rằng các số CPU không nhất thiết phải liên tục. Bạn trả lại nó bằng put_cpu() khi bạn đã xong.

Nếu bạn biết bạn không thể bị ưu tiên bởi một nhiệm vụ khác (tức là bạn đang ở trong làm gián đoạn bối cảnh hoặc bị vô hiệu hóa quyền ưu tiên), bạn có thể sử dụng smp_processor_id().

ZZ0000ZZ/ZZ0001ZZ/ZZ0002ZZ

Được xác định trong ZZ0000ZZ

Sau khi khởi động, kernel sẽ giải phóng một phần đặc biệt; chức năng được đánh dấu bằng ZZ0000ZZ và cấu trúc dữ liệu được đánh dấu bằng ZZ0001ZZ bị loại bỏ sau khi khởi động xong: các mô-đun tương tự sẽ loại bỏ bộ nhớ này sau khởi tạo. ZZ0002ZZ được sử dụng để khai báo một hàm chỉ bắt buộc khi thoát: chức năng sẽ bị loại bỏ nếu tệp này không được biên dịch dưới dạng mô-đun. Xem tập tin tiêu đề để sử dụng. Lưu ý rằng nó làm cho không ý nghĩa đối với một chức năng được đánh dấu bằng ZZ0003ZZ sẽ được xuất sang các mô-đun với EXPORT_SYMBOL() hoặc EXPORT_SYMBOL_GPL()- cái này sẽ vỡ.

__initcall()/module_init()

Được xác định trong ZZ0000ZZ / ZZ0001ZZ

Nhiều phần của kernel được phục vụ tốt như một mô-đun (các phần có thể tải động của kernel). Sử dụng module_init()module_exit() macro nó rất dễ viết mã mà không cần #ifdefs, có thể hoạt động cả dưới dạng mô-đun hoặc được tích hợp vào kernel.

Macro module_init() xác định chức năng nào sẽ được thực hiện được gọi tại thời điểm chèn mô-đun (nếu tệp được biên dịch dưới dạng mô-đun), hoặc lúc khởi động: nếu tệp không được biên dịch thành một mô-đun thì macro module_init() trở nên tương đương với __initcall(), thông qua phép thuật liên kết đảm bảo rằng chức năng được gọi khi khởi động.

Hàm có thể trả về số lỗi âm để gây ra việc tải mô-đun thất bại (không may là điều này không có tác dụng nếu mô-đun được biên dịch vào hạt nhân). Hàm này được gọi trong ngữ cảnh người dùng với ngắt được kích hoạt, vì vậy nó có thể ngủ.

module_exit()

Được xác định trong ZZ0000ZZ

Macro này xác định chức năng được gọi tại thời điểm loại bỏ mô-đun (hoặc không bao giờ, trong trường hợp tệp được biên dịch vào kernel). Nó sẽ chỉ được gọi nếu số lượng sử dụng mô-đun đã đạt tới 0. Chức năng này có thể cũng ngủ, nhưng không thể thất bại: mọi thứ phải được dọn dẹp trước thời gian nó quay trở lại.

Lưu ý rằng macro này là tùy chọn: nếu nó không xuất hiện, mô-đun của bạn sẽ không thể tháo rời được (ngoại trừ ‘rmmod -f’).

try_module_get()/module_put()

Được xác định trong ZZ0000ZZ

Chúng thao túng số lượng sử dụng mô-đun, để bảo vệ chống lại việc loại bỏ (a mô-đun cũng không thể bị xóa nếu một mô-đun khác sử dụng một trong các mô-đun đã xuất của nó ký hiệu: xem bên dưới). Trước khi gọi vào mã mô-đun, bạn nên gọi try_module_get() trên mô-đun đó: nếu thất bại thì mô-đun đang bị xóa và bạn nên hành động như thể nó không có ở đó. Nếu không, bạn có thể vào mô-đun một cách an toàn và gọi module_put() khi bạn hoàn thành.

Hầu hết các cấu trúc có thể đăng ký đều có trường chủ sở hữu, chẳng hạn như trong Cấu trúc ZZ0000ZZ. Đặt trường này thành macro ZZ0001ZZ.

Hàng đợi ZZ0000ZZ

ZZ0000ZZ

Hàng đợi được sử dụng để đợi ai đó đánh thức bạn vào một thời điểm nhất định. điều kiện là đúng. Chúng phải được sử dụng cẩn thận để đảm bảo không có tình trạng cuộc đua. Bạn khai báo ZZ0000ZZ và sau đó xử lý muốn chờ điều kiện đó hãy khai báo ZZ0001ZZ đề cập đến chính họ và đặt nó vào hàng đợi.

Khai báo

Bạn khai báo ZZ0000ZZ bằng cách sử dụng macro DECLARE_WAIT_QUEUE_HEAD() hoặc sử dụng thói quen init_waitqueue_head() trong quá trình khởi tạo của bạn mã.

Xếp hàng

Việc đặt mình vào hàng chờ khá phức tạp vì bạn phải đặt mình vào hàng đợi trước khi kiểm tra điều kiện. có một macro để thực hiện việc này: Wait_event_interruptible() (ZZ0000ZZ) Đối số đầu tiên là đầu hàng chờ và thứ hai là một biểu thức được đánh giá; macro trả về 0 khi biểu thức này là đúng hoặc ZZ0001ZZ nếu nhận được tín hiệu. các Phiên bản wait_event() bỏ qua tín hiệu.

Đánh thức các nhiệm vụ xếp hàng đợi

Gọi Wake_up() (ZZ0000ZZ), nó sẽ đánh thức lên mọi tiến trình trong hàng đợi. Ngoại lệ là nếu một người có ZZ0001ZZ được đặt, trong trường hợp đó phần còn lại của hàng đợi sẽ không được đánh thức. Có sẵn các biến thể khác của chức năng cơ bản này trong cùng một tiêu đề.

Hoạt động nguyên tử

Một số hoạt động nhất định được đảm bảo nguyên tử trên tất cả các nền tảng. đầu tiên lớp hoạt động hoạt động trên ZZ0000ZZ (ZZ0002ZZ); cái này chứa một số nguyên có dấu (dài ít nhất 32 bit) và bạn phải sử dụng các hàm này để thao tác hoặc đọc các biến ZZ0001ZZ. Atomic_read()Atomic_set() lấy và thiết lập bộ đếm, Atomic_add(), Atomic_sub(), Atomic_inc(), Atomic_dec()Atomic_dec_and_test() (trả về true nếu đúng giảm xuống bằng không).

Đúng. Nó trả về true (tức là != 0) nếu biến nguyên tử bằng 0.

Lưu ý rằng các hàm này chậm hơn so với số học thông thường, và do đó không nên sử dụng một cách không cần thiết.

Lớp hoạt động nguyên tử thứ hai là các hoạt động bit nguyên tử trên một ZZ0000ZZ, được định nghĩa trong ZZ0001ZZ. Những cái này các hoạt động thường lấy một con trỏ tới mẫu bit và một chút số: 0 là bit ít quan trọng nhất. set_bit(), Clear_bit()Change_bit() được đặt, xóa, và lật bit đã cho. test_and_set_bit(), test_and_clear_bit()test_and_change_bit() làm điều tương tự, ngoại trừ return đúng nếu bit đã được đặt trước đó; những điều này đặc biệt hữu ích cho cờ cài đặt nguyên tử.

Có thể gọi các hoạt động này với chỉ số bit lớn hơn ZZ0000ZZ. Hành vi kết quả là lạ trên big-endian mặc dù vậy, tốt nhất là bạn không nên làm điều này.

Biểu tượng

Trong kernel thích hợp, các quy tắc liên kết thông thường được áp dụng (nghĩa là trừ khi biểu tượng được khai báo là phạm vi tệp với từ khóa ZZ0000ZZ, nó có thể được sử dụng ở bất cứ đâu trong kernel). Tuy nhiên, đối với các mô-đun, một điều đặc biệt bảng ký hiệu đã xuất được giữ lại để giới hạn các điểm vào hạt nhân thích hợp. Các mô-đun cũng có thể xuất các ký hiệu.

EXPORT_SYMBOL()

Được xác định trong ZZ0000ZZ

Đây là phương pháp cổ điển để xuất biểu tượng: được tải động các mô-đun sẽ có thể sử dụng biểu tượng như bình thường.

EXPORT_SYMBOL_GPL()

Được xác định trong ZZ0000ZZ

Tương tự như EXPORT_SYMBOL() ngoại trừ các ký hiệu được xuất bởi EXPORT_SYMBOL_GPL() chỉ có thể được nhìn thấy bởi mô-đun có MODULE_LICENSE() chỉ định GPLv2 giấy phép tương thích. Nó ngụ ý rằng chức năng này được coi là một vấn đề triển khai nội bộ và không thực sự là một giao diện. Một số Tuy nhiên, người bảo trì và nhà phát triển có thể yêu cầu EXPORT_SYMBOL_GPL() khi thêm bất kỳ API hoặc chức năng mới nào.

EXPORT_SYMBOL_NS()

Được xác định trong ZZ0000ZZ

Đây là biến thể của EXPORT_SYMBOL() cho phép chỉ định ký hiệu không gian tên. Không gian tên biểu tượng được ghi lại trong Tài liệu/core-api/symbol-namespaces.rst

EXPORT_SYMBOL_NS_GPL()

Được xác định trong ZZ0000ZZ

Đây là biến thể của EXPORT_SYMBOL_GPL() cho phép chỉ định ký hiệu không gian tên. Không gian tên biểu tượng được ghi lại trong Tài liệu/core-api/symbol-namespaces.rst

Các thói quen và quy ước

Danh sách liên kết đôi ZZ0000ZZ

Đã từng có ba bộ quy trình danh sách liên kết trong kernel tiêu đề, nhưng cái này là người chiến thắng. Nếu bạn không có một số đặc biệt nhu cầu cấp thiết về một danh sách duy nhất, đó là một lựa chọn tốt.

Đặc biệt, list_for_each_entry() rất hữu ích.

Quy ước trả lại

Đối với mã được gọi trong ngữ cảnh của người dùng, việc vi phạm quy ước C là điều rất phổ biến, và trả về 0 nếu thành công và số lỗi âm (ví dụ: ZZ0000ZZ) cho thất bại. Điều này ban đầu có thể không trực quan nhưng nó khá phổ biến trong hạt nhân.

Sử dụng ERR_PTR() (ZZ0000ZZ) để mã hóa một số lỗi âm vào một con trỏ và IS_ERR()PTR_ERR() để lấy nó ra lần nữa: tránh tách biệt tham số con trỏ cho số lỗi. Icky, nhưng theo một cách tốt.

Biên soạn đột phá

Linus và các nhà phát triển khác đôi khi thay đổi chức năng hoặc cấu trúc tên trong hạt nhân phát triển; việc này không được thực hiện chỉ để giữ mọi người tiếp tục ngón chân của họ: nó phản ánh một sự thay đổi cơ bản (ví dụ: không còn có thể được gọi khi bị gián đoạn hoặc thực hiện kiểm tra bổ sung hoặc không thực hiện kiểm tra đã bị bắt trước đó). Thông thường điều này đi kèm với một khá ghi chú đầy đủ vào danh sách gửi thư phát triển hạt nhân thích hợp; tìm kiếm các kho lưu trữ. Chỉ cần thực hiện thay thế toàn cục trên tệp thường tạo ra thứ ZZ0000ZZ.

Đang khởi tạo các thành viên cấu trúc

Phương pháp khởi tạo cấu trúc ưa thích là sử dụng được chỉ định công cụ khởi tạo, như được xác định bởi ISO C99, ví dụ:

cấu trúc tĩnh block_device_Operation opt_fops = {

.open = opt_open, .release = opt_release, .ioctl = opt_ioctl, .check_media_change = opt_media_change,

};

Điều này giúp bạn dễ dàng grep và làm rõ cấu trúc nào các trường được thiết lập. Bạn nên làm điều này vì nó trông rất ngầu.

Tiện ích mở rộng GNU

Tiện ích mở rộng GNU được cho phép rõ ràng trong nhân Linux. Lưu ý rằng một số cái phức tạp hơn không được hỗ trợ tốt, do thiếu sử dụng chung, nhưng những điều sau đây được coi là tiêu chuẩn (xem GCC phần trang thông tin “Tiện ích mở rộng C” để biết thêm chi tiết - Có, thực sự là thông tin trang, trang man chỉ là một bản tóm tắt ngắn gọn về nội dung trong thông tin).

  • Chức năng nội tuyến

  • Biểu thức câu lệnh (tức là cấu trúc ({ và }).

  • Khai báo thuộc tính của hàm/biến/kiểu

    (__thuộc tính__)

  • loại

  • Mảng có độ dài bằng không

  • Các biến thể macro

  • Tính toán trên con trỏ void

  • Bộ khởi tạo không cố định

  • Hướng dẫn lắp ráp (không nằm ngoài Arch/ và bao gồm/asm/)

  • Tên hàm dưới dạng chuỗi (__func__).

  • __buildin_constant_p()

Hãy thận trọng khi sử dụng long long trong kernel, mã gcc tạo ra cho điều đó thật kinh khủng và tệ hơn nữa: phép chia và phép nhân không có tác dụng i386 vì các chức năng thời gian chạy GCC cho nó bị thiếu trong môi trường hạt nhân.

C++

Sử dụng C++ trong kernel thường là một ý tưởng tồi, vì kernel có không cung cấp môi trường thời gian chạy cần thiết và các tệp bao gồm không được thử nghiệm cho nó. Vẫn có thể, nhưng không được khuyến khích. Nếu bạn thực sự muốn làm điều này, ít nhất hãy quên đi những ngoại lệ.

#if

Nói chung, việc sử dụng macro trong các tệp tiêu đề (hoặc tại phần đầu của các tệp .c) để trừu tượng hóa các hàm thay vì sử dụng `#if’ các câu lệnh tiền xử lý xuyên suốt mã nguồn.

Đưa nội dung của bạn vào hạt nhân

Để hoàn thiện nội dung của bạn để đưa vào chính thức, hoặc thậm chí để tạo một bản vá gọn gàng, có công việc hành chính phải làm:

  • Tìm ra ai là chủ sở hữu của mã bạn đang sửa đổi. Nhìn kìa

    ở đầu tệp nguồn, bên trong tệp ZZ0000ZZ và cuối cùng là trong tệp ZZ0001ZZ. Bạn nên phối hợp với những mọi người để đảm bảo rằng bạn không nỗ lực gấp đôi hoặc thử điều gì đó điều đó đã bị từ chối rồi.

Đảm bảo bạn đặt tên và địa chỉ email của mình ở đầu bất kỳ tệp nào

bạn tạo hoặc sửa đổi đáng kể. Đây là nơi đầu tiên mọi người sẽ xem xét khi họ tìm thấy lỗi hoặc khi ZZ0000ZZ muốn thực hiện thay đổi.

  • Thông thường bạn muốn có một tùy chọn cấu hình cho bản hack kernel của mình. Chỉnh sửa

    ZZ0000ZZ trong thư mục thích hợp. Ngôn ngữ cấu hình là sử dụng đơn giản bằng cách cắt và dán, đồng thời có tài liệu đầy đủ về Tài liệu/kbuild/kconfig-lingu.rst.

Trong mô tả của bạn về tùy chọn, hãy đảm bảo bạn giải quyết cả hai

người dùng chuyên gia và người dùng không biết gì về tính năng của bạn. Đề cập đến sự không tương thích và các vấn đề ở đây. ZZ0000ZZ kết thúc của bạn mô tả với “nếu nghi ngờ, hãy nói N” (hoặc đôi khi, `Y’); cái này dành cho những người không hiểu bạn đang nói về điều gì.

  • Chỉnh sửa ZZ0000ZZ: các biến CONFIG được xuất ra đây nên bạn

    thường chỉ có thể thêm dòng “obj-$(CONFIG_xxx) += xxx.o”. Cú pháp được ghi lại trong Linux Kernel Makefiles.

  • Hãy đặt mình vào ZZ0000ZZ nếu bạn cân nhắc những gì mình đã làm

    đáng chú ý, thường nằm ngoài một tệp duy nhất (tên của bạn phải ở ở đầu tệp nguồn). ZZ0001ZZ có nghĩa là bạn muốn trở thành được tư vấn khi có thay đổi đối với hệ thống con và nghe về lỗi; nó ngụ ý một cam kết vượt mức cho một số phần của mã.

  • Cuối cùng, đừng quên đọc

    Tài liệu/quy trình/gửi-patches.rst.

Cantrip hạt nhân

Một số mục yêu thích từ việc duyệt nguồn. Hãy thêm vào danh sách này.

ZZ0000ZZ:

#define ndelay(n) (__buildin_constant_p(n) ? \

((n) > 20000 ? __bad_ndelay() : __const_udelay((n) * 5ul)) : __ndelay(n))

ZZ0000ZZ:

/*
  • Con trỏ hạt nhân có thông tin dư thừa nên chúng ta có thể sử dụng

  • sơ đồ trong đó chúng tôi có thể trả về mã lỗi hoặc mã nha khoa

  • con trỏ có cùng giá trị trả về.

  • Đây phải là thứ tùy theo từng kiến trúc, để cho phép các

  • quyết định lỗi và con trỏ.

*/ #define ERR_PTR(err) ((void *)((dài)(err))) #define PTR_ERR(ptr) ((dài)(ptr)) #define IS_ERR(ptr) ((dài không dấu)(ptr) > (dài không dấu)(-1000))

ZZ0000ZZ:

#define copy_to_user(đến,từ,n) \
(__buildin_constant_p(n) ?

__constant_copy_to_user((to),(from),(n)) : __generic_copy_to_user((đến),(từ),(n)))

ZZ0000ZZ:

/*
  • Sun người không thể đánh vần có giá trị chết tiệt. thực sự là “khả năng tương thích”.

  • Ít nhất chúng tôi ZZ0000ZZ chúng tôi không thể đánh vần và sử dụng trình kiểm tra chính tả.

*/

/* Uh, thực ra Linus là tôi không biết đánh vần. Quá nhiều âm u
  • Sparc lắp ráp sẽ làm điều này với bạn.

*/

C_LABEL(cputypvar):

.asciz “khả năng tương thích”

/* Đã thử nghiệm trên SS-5, SS-10. Có lẽ ai đó ở Sun đã áp dụng trình kiểm tra chính tả. */

.căn chỉnh 4

C_LABEL(cputypvar_sun4m):

.asciz “tương thích”

ZZ0000ZZ:

/* Sun, anh không thể đánh bại tôi, anh không thể.  Đừng cố gắng nữa
  • từ bỏ. Tôi nghiêm túc đấy, tôi sẽ đá chết người

  • ra khỏi bạn, trò chơi kết thúc, tắt đèn.

*/

Cảm ơn

Cảm ơn Andi Kleen vì ý tưởng, trả lời các câu hỏi của tôi, sửa chữa lỗi chính tả, điền nội dung, v.v. Philipp Rumpf để biết thêm chính tả và sửa lỗi rõ ràng và một số điểm tuyệt vời không rõ ràng. Werner Almesberger vì đã cho tôi một bản tóm tắt tuyệt vời về vô hiệu hóa_irq() và Jes Sorensen và Andrea Arcangeli đã bổ sung thêm những cảnh báo. Michael Elizabeth Chastain để kiểm tra và thêm vào phần Cấu hình. Telsa Gwynne cho dạy tôi DocBook.