2013-02-22 16 views
7

Trong linux/arch/x86/include/asm/switch_to.h, có định nghĩa về vĩ mô switch_to, các đường chính mà làm thread thực switch phép lạ đọc như thế này (cho đến Linux 4.7 khi nó thay đổi):Tại sao switch_to sử dụng push + jmp + ret để thay đổi EIP, thay vì jmp trực tiếp?

asm volatile("pushfl\n\t"  /* save flags */ \ 
       pushl %%ebp\n\t"  /* save EBP */ \ 
       "movl %%esp,%[prev_sp]\n\t" /* save ESP */ \ 
       "movl %[next_sp],%%esp\n\t" /* restore ESP */ \ 
       "movl $1f,%[prev_ip]\n\t" /* save EIP */ \ 
       "pushl %[next_ip]\n\t" /* restore EIP */ \ 
       __switch_canary     \ 
       "jmp __switch_to\n" /* regparm call */ \ 
       "1:\t"      \ 
       "popl %%ebp\n\t"  /* restore EBP */ \ 
       "popfl\n"   /* restore flags */ \ 

Các toán hạng được đặt tên có bộ nhớ hạn chế như [prev_sp] "=m" (prev->thread.sp) . __switch_canary được xác định là không có gì trừ khi CONFIG_CC_STACKPROTECTOR được xác định (sau đó là tải và lưu trữ sử dụng %ebx).

Tôi hiểu cách thức hoạt động, giống như con trỏ backup kernel stack/phục hồi, và làm thế nào push next->eipjmp __switch_to với một hướng dẫn ret ở phần cuối của các chức năng, mà thực sự là một "giả" lệnh gọi phù hợp với thực ret hướng dẫn và tạo hiệu quả cho next->eip điểm trả về của chuỗi tiếp theo.

Điều tôi không hiểu là, tại sao lại là hack? Tại sao không chỉ call __switch_to, sau đó là ret, jmp đến next->eip, sạch hơn và thân thiện với người đọc hơn.

Trả lời

5

Có hai lý do để thực hiện theo cách này.

Một là cho phép linh hoạt hoàn toàn việc phân bổ toán hạng/đăng ký cho [next_ip]. Nếu bạn muốn để có thể làm jmp %[next_ip]sau các call __switch_to sau đó nó là cần thiết để có %[next_ip] phân bổ cho một thanh ghi không bay hơi (tức là một trong đó, bởi các định nghĩa của ABI, sẽ giữ lại giá trị của nó khi thực hiện một cuộc gọi chức năng).

Điều đó giới thiệu một hạn chế về khả năng tối ưu hóa của trình biên dịch và mã kết quả cho context_switch() ('người gọi' - nơi sử dụng switch_to()) có thể không tốt bằng. Nhưng vì lợi ích gì?

Vâng - đó là nơi lý do thứ hai đến, không có, thực sự, bởi vì call __switch_to sẽ tương đương với:

pushl 1f 
jmp __switch_to 
1: jmp %[next_ip] 

tức là nó đẩy địa chỉ trả lại; bạn sẽ kết thúc bằng một chuỗi push/jmp (== call)/ret/jmp trong khi nếu bạn không muốn quay lại địa điểm này (và mã này không), bạn tiết kiệm chi nhánh mã bằng cách "giả mạo" cuộc gọi vì bạn chỉ phải làm push/jmp/ret. Mã tự tạo cho mình đuôi đệ quy tại đây.

Vâng, đó là một tối ưu hóa nhỏ, nhưng tránh một nhánh làm giảm độ trễ và độ trễ là rất quan trọng đối với các công tắc ngữ cảnh.

+2

Nhưng sẽ không hiệu quả khi giết ngăn xếp dự đoán ngược? – harold

+0

có - nhưng 'jmp' để đăng ký mục tiêu thực hiện điều đó là tốt, vì bạn _never_ muốn trở về' switch_to() ', thực sự (cho đến khi chuyển đổi ngữ cảnh tiếp theo). Không có sự khác biệt giữa cả hai như xa như vậy. –

+0

@harold: Điểm tốt; các nội dung hiện tại của ngăn xếp dự báo địa chỉ trả về có giá trị sau khi chuyển đổi ngữ cảnh cho trả về từ 'context_switch()' ([trong 'kernel/sched/core.c'] (http://elixir.free-electrons.com /linux/v4.6/source/kernel/sched/core.c#L2752)), và có thể một vài cấp sao lưu ngăn xếp cuộc gọi vào bộ lập lịch biểu (cho đến khi backtraces của họ phân kỳ).Nhưng điều đó chỉ đúng nếu '% [next_ip]' luôn luôn là/thường bên trong switch_to; nó đặt 'prev_ip' theo cách đó, nhưng có lẽ đó không phải là giá trị phổ biến nhất (sự tiền hạt nhân có thể để nó ở một nơi khác?) –

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