Các vòng CPU là sự phân biệt rõ ràng nhất
Trong chế độ được bảo vệ x86, CPU luôn ở một trong 4 vòng. Nhân Linux chỉ sử dụng 0 và 3:
- 0 cho nhân
- 3 cho người dùng
Đây là định nghĩa khó nhất và nhanh nhất của kernel vs userland.
Tại sao Linux không sử dụng vòng 1 và 2: https://stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent- used
Làm thế nào là vòng hiện tại được xác định?
Vòng hiện tại được chọn bởi sự kết hợp của:
bảng mô tả toàn cầu: một bảng trong bộ nhớ của các mục GDT và mỗi mục có một trường Privl
mã hóa vòng.
Lệnh LGDT đặt địa chỉ cho bảng mô tả hiện tại.
Xem thêm: http://wiki.osdev.org/Global_Descriptor_Table
phân đoạn đăng ký CS, DS, v.v., trỏ đến chỉ mục của một mục trong GDT.
Ví dụ: CS = 0
có nghĩa là mục nhập đầu tiên của GDT hiện đang hoạt động cho mã thực thi.
Mỗi chiếc nhẫn có thể làm gì?
Chip CPU được chế tạo vật lý sao cho:
vòng 0 có thể làm bất cứ điều gì
vòng 3 không thể chạy một số hướng dẫn và ghi vào một số thanh ghi, đáng chú ý nhất là:
không thể thay đổi chiếc nhẫn của riêng mình! Nếu không, nó có thể tự đặt thành số 0 và nhẫn sẽ vô dụng.
Nói cách khác, không thể sửa đổi bộ mô tả phân đoạn hiện tại , xác định vòng hiện tại.
không thể sửa đổi các bảng trang: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
Nói cách khác, không thể sửa đổi thanh ghi CR3 và chính việc phân trang ngăn không cho sửa đổi các bảng trang.
Điều này ngăn một tiến trình nhìn thấy bộ nhớ của các tiến trình khác vì lý do bảo mật / dễ dàng cho các lý do lập trình.
không thể đăng ký xử lý ngắt. Chúng được cấu hình bằng cách ghi vào các vị trí bộ nhớ, điều này cũng được ngăn chặn bằng cách phân trang.
Trình xử lý chạy trong vòng 0 và sẽ phá vỡ mô hình bảo mật.
Nói cách khác, không thể sử dụng các hướng dẫn LGDT và LIDT.
không thể thực hiện các hướng dẫn IO như in
và out
, do đó có quyền truy cập phần cứng tùy ý.
Mặt khác, ví dụ, quyền truy cập tệp sẽ vô dụng nếu bất kỳ chương trình nào có thể đọc trực tiếp từ đĩa.
Chính xác hơn là nhờ Michael Petch : hệ điều hành thực sự có thể cho phép các hướng dẫn IO trên vòng 3, điều này thực sự được kiểm soát bởi phân đoạn trạng thái Nhiệm vụ .
Điều không thể xảy ra là vòng 3 sẽ tự cho phép làm như vậy nếu nó không có nó ở vị trí đầu tiên.
Linux luôn không cho phép nó. Xem thêm: https://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss
Làm thế nào để các chương trình và hệ điều hành chuyển tiếp giữa các vòng?
khi CPU được bật, nó bắt đầu chạy chương trình ban đầu ở vòng 0 (cũng khá tốt, nhưng đó là một xấp xỉ tốt). Bạn có thể nghĩ chương trình ban đầu này là kernel (nhưng thông thường nó là bootloader sau đó gọi kernel vẫn ở vòng 0).
khi một quá trình userland muốn kernel thực hiện một cái gì đó cho nó như ghi vào một tệp, nó sử dụng một lệnh tạo ra một ngắt như int 0x80
hoặcsyscall
để báo hiệu kernel. x86-64 Linux tòa nhà xin chào ví dụ thế giới:
.data
hello_world:
.ascii "hello world\n"
hello_world_len = . - hello_world
.text
.global _start
_start:
/* write */
mov $1, %rax
mov $1, %rdi
mov $hello_world, %rsi
mov $hello_world_len, %rdx
syscall
/* exit */
mov $60, %rax
mov $0, %rdi
syscall
biên dịch và chạy:
as -o hello_world.o hello_world.S
ld -o hello_world.out hello_world.o
./hello_world.out
GitHub ngược dòng .
Khi điều này xảy ra, CPU gọi một trình xử lý gọi lại ngắt mà kernel đã đăng ký khi khởi động. Dưới đây là một ví dụ cụ thể về thanh ghi đăng ký một trình xử lý và sử dụng nó .
Trình xử lý này chạy trong vòng 0, quyết định xem kernel có cho phép hành động này không, thực hiện hành động đó và khởi động lại chương trình userland trong vòng 3. x86_64
khi exec
cuộc gọi hệ thống được sử dụng (hoặc khi kernel sẽ bắt đầu/init
), kernel sẽ chuẩn bị các thanh ghi và bộ nhớ của tiến trình người dùng mới, sau đó nó nhảy đến điểm vào và chuyển CPU sang vòng 3
Nếu chương trình cố gắng làm một cái gì đó nghịch ngợm như ghi vào một thanh ghi bị cấm hoặc địa chỉ bộ nhớ (vì phân trang), CPU cũng gọi một số trình xử lý gọi lại kernel trong vòng 0.
Nhưng vì vùng người dùng nghịch ngợm, hạt nhân có thể giết quá trình lần này hoặc đưa ra cảnh báo bằng tín hiệu.
Khi kernel khởi động, nó sẽ thiết lập đồng hồ phần cứng với một số tần số cố định, tạo ra các ngắt theo định kỳ.
Đồng hồ phần cứng này tạo ra các ngắt chạy vòng 0 và cho phép nó lập lịch trình quá trình người dùng nào thức dậy.
Bằng cách này, lập lịch có thể xảy ra ngay cả khi các quy trình không thực hiện bất kỳ cuộc gọi hệ thống nào.
Điểm có nhiều vòng là gì?
Có hai ưu điểm chính của việc tách kernel và userland:
- việc tạo ra các chương trình sẽ dễ dàng hơn vì bạn chắc chắn sẽ không can thiệp vào chương trình kia. Ví dụ, một quy trình người dùng không phải lo lắng về việc ghi đè bộ nhớ của chương trình khác vì phân trang, cũng như không đặt phần cứng ở trạng thái không hợp lệ cho quy trình khác.
- nó an toàn hơn Ví dụ: quyền truy cập tệp và phân tách bộ nhớ có thể ngăn ứng dụng hack đọc dữ liệu ngân hàng của bạn. Tất nhiên, điều này cho rằng bạn tin tưởng hạt nhân.
Làm thế nào để chơi xung quanh nó?
Tôi đã tạo một thiết lập kim loại trần nên là cách tốt để thao tác trực tiếp với nhẫn: https://github.com/cirosantilli/x86-bare-metal-examples
Thật không may, tôi đã không đủ kiên nhẫn để tạo một ví dụ về vùng người dùng, nhưng tôi đã đi xa như thiết lập phân trang, vì vậy vùng sử dụng nên khả thi. Tôi muốn thấy một yêu cầu kéo.
Ngoài ra, các mô-đun hạt nhân Linux chạy trong vòng 0, vì vậy bạn có thể sử dụng chúng để thử các hoạt động đặc quyền, ví dụ: đọc các thanh ghi điều khiển: https://stackoverflow.com/questions/7415515/how-to-access-the-control-registers -cr0-cr2-cr3-from-a-chương trình-nhận-phân đoạn / 7419306 # 7419306
Dưới đây là một thiết lập QEMU + Buildroot thuận tiện để dùng thử mà không làm chết máy chủ của bạn.
Nhược điểm của các mô-đun hạt nhân là các kthread khác đang chạy và có thể can thiệp vào các thử nghiệm của bạn. Nhưng trên lý thuyết, bạn có thể tiếp quản tất cả các trình xử lý ngắt với mô-đun hạt nhân của mình và sở hữu hệ thống, đó thực sự là một dự án thú vị.
Vòng âm
Mặc dù các vòng âm không thực sự được tham chiếu trong hướng dẫn sử dụng Intel, nhưng thực tế có các chế độ CPU có khả năng hơn cả vòng 0 và do đó rất phù hợp với tên "vòng âm".
Một ví dụ là chế độ hypanneror được sử dụng trong ảo hóa.
Để biết thêm chi tiết, hãy xem: https://security.stackexchange.com/questions/129098/what-is-protection-ring-1
CÁNH TAY
Trong ARM, các vòng được gọi là Cấp độ ngoại lệ thay vào đó, nhưng các ý tưởng chính vẫn giữ nguyên.
Có 4 cấp độ ngoại lệ trong ARMv8, thường được sử dụng là:
EL0: người dùng
EL1: kernel ("người giám sát" theo thuật ngữ ARM).
Được nhập với svc
hướng dẫn (SuperVisor Call), trước đây được biết đến như swi
trước khi hợp nhất , là hướng dẫn được sử dụng để thực hiện các cuộc gọi hệ thống Linux. Xin chào thế giới ví dụ ARMv8:
.text
.global _start
_start:
/* write */
mov x0, 1
ldr x1, =msg
ldr x2, =len
mov x8, 64
svc 0
/* exit */
mov x0, 0
mov x8, 93
svc 0
msg:
.ascii "hello syscall v8\n"
len = . - msg
GitHub ngược dòng .
Kiểm tra nó với QEMU trên Ubuntu 16.04:
sudo apt-get install qemu-user gcc-arm-linux-gnueabihf
arm-linux-gnueabihf-as -o hello.o hello.S
arm-linux-gnueabihf-ld -o hello hello.o
qemu-arm hello
Dưới đây là một ví dụ cụ thể về thanh ghi đăng ký trình xử lý SVC và thực hiện cuộc gọi SVC .
EL2: hypannerors , ví dụ Xen .
Được nhập với hvc
hướng dẫn (Cuộc gọi HyperVisor).
Một trình ảo hóa là cho một hệ điều hành, một hệ điều hành là gì đối với người dùng.
Ví dụ, Xen cho phép bạn chạy nhiều HĐH như Linux hoặc Windows trên cùng một hệ thống cùng một lúc và nó cách ly các HĐH với nhau để bảo mật và dễ gỡ lỗi, giống như Linux làm cho các chương trình người dùng.
Hypervisor là một phần quan trọng của cơ sở hạ tầng đám mây ngày nay: chúng cho phép nhiều máy chủ chạy trên một phần cứng duy nhất, giữ mức sử dụng phần cứng luôn ở mức gần 100% và tiết kiệm rất nhiều tiền.
AWS đã sử dụng Xen cho đến năm 2017 khi việc chuyển sang KVM đưa ra tin tức .
EL3: một cấp độ khác. Ví dụ TODO.
Được nhập với smc
hướng dẫn (Cuộc gọi Chế độ Bảo mật)
Các ARMv8 Kiến trúc mô hình tham chiếu DDI 0487C.a - Chương D1 - Hệ thống AArch64 Cấp Programmer của Người mẫu - Hình D1-1 minh họa này đẹp:
Lưu ý cách ARM, có thể do lợi ích của nhận thức muộn, có quy ước đặt tên tốt hơn cho các cấp đặc quyền so với x86, mà không cần mức âm: 0 là thấp hơn và cao nhất 3. Mức cao hơn có xu hướng được tạo ra thường xuyên hơn so với mức thấp hơn.
EL hiện tại có thể được truy vấn theo MRS
hướng dẫn: https://stackoverflow.com/questions/31787617/what-is-the-civerse-execut-mode-exception-level-etc
ARM không yêu cầu tất cả các cấp ngoại lệ phải có mặt để cho phép các triển khai không cần tính năng này để tiết kiệm diện tích chip. ARMv8 "Cấp độ ngoại lệ" cho biết:
Việc triển khai có thể không bao gồm tất cả các cấp độ Ngoại lệ. Tất cả các triển khai phải bao gồm EL0 và EL1. EL2 và EL3 là tùy chọn.
Ví dụ, QEMU mặc định cho EL1, nhưng EL2 và EL3 có thể được bật bằng các tùy chọn dòng lệnh: https://stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulation-a53-power-up
Đoạn mã được thử nghiệm trên Ubuntu 18.10.