จะเกิดอะไรขึ้นเมื่อฉันเรียกใช้ไฟล์ในเชลล์


32

ดังนั้นฉันคิดว่าฉันมีความเข้าใจที่ดีในเรื่องนี้ แต่เพียงแค่ทดสอบ (เพื่อตอบสนองต่อการสนทนาที่ฉันไม่เห็นด้วยกับใครบางคน) และพบว่าความเข้าใจของฉันมีข้อบกพร่อง ...

ในรายละเอียดมากที่สุดจะเกิดอะไรขึ้นเมื่อฉันเรียกใช้ไฟล์ในเชลล์? สิ่งที่ฉันหมายถึงคือถ้าฉันพิมพ์: ./somefile some argumentsลงในเปลือกของฉันและกดย้อนกลับ (และsomefileมีอยู่ใน cwd และฉันได้อ่าน + ดำเนินการสิทธิ์บนsomefile) แล้วเกิดอะไรขึ้นภายใต้ประทุน?

ฉันคิดว่าคำตอบคือ:

  1. เปลือกทำ syscall ให้execผ่านเส้นทางไปsomefile
  2. เคอร์เนลตรวจสอบsomefileและดูหมายเลขมายากลของไฟล์เพื่อตรวจสอบว่าเป็นรูปแบบที่โปรเซสเซอร์สามารถจัดการได้หรือไม่
  3. หากหมายเลขมายากลระบุว่าไฟล์อยู่ในรูปแบบที่โปรเซสเซอร์สามารถใช้งานได้
    1. กระบวนการใหม่ถูกสร้างขึ้น (โดยมีรายการในตารางกระบวนการ)
    2. somefileถูกอ่าน / แมปกับหน่วยความจำ สแต็กถูกสร้างขึ้นและการดำเนินการข้ามไปยังจุดเริ่มต้นของรหัสsomefileโดยARGVเริ่มต้นไปที่อาร์เรย์ของพารามิเตอร์ (a char**, ["some","arguments"])
  4. หากจำนวนมายากลเป็นshebangแล้วexec()spawns กระบวนการใหม่ดังกล่าวข้างต้น แต่ปฏิบัติการที่ใช้เป็นล่ามอ้างอิงโดย shebang (เช่น/bin/bashหรือ/bin/perl) และsomefileถูกส่งไปยังSTDIN
  5. หากไฟล์ไม่มีหมายเลขเวทย์มนตร์ที่ถูกต้องแสดงว่ามีข้อผิดพลาดเช่น "ไฟล์ไม่ถูกต้อง (หมายเลขเวทย์มนตร์ที่ไม่ดี): เกิดข้อผิดพลาดในรูปแบบ Exec"

อย่างไรก็ตามมีคนบอกฉันว่าถ้าไฟล์เป็นข้อความธรรมดาเชลล์จะพยายามเรียกใช้คำสั่ง (เช่นถ้าฉันพิมพ์bash somefile) ฉันไม่เชื่อเรื่องนี้ แต่ฉันแค่ลองมันและมันก็ถูกต้อง ดังนั้นฉันจึงมีความเข้าใจผิดอย่างชัดเจนเกี่ยวกับสิ่งที่เกิดขึ้นจริงที่นี่และต้องการที่จะเข้าใจกลไก

จะเกิดอะไรขึ้นเมื่อฉันเรียกใช้ไฟล์ในเชลล์ (ในรายละเอียดมากพอสมควร ... )


ไม่มีอะไรทดแทนที่สมบูรณ์แบบสำหรับการดูซอร์สโค้ดเพื่อความเข้าใจอย่างลึกซึ้ง
Wildcard

1
@ Wildcard นั่นคือสิ่งที่ฉันกำลังทำอยู่ตอนนี้จริง ๆ แล้ว :-) ถ้าทำได้ฉันจะตอบคำถามของตัวเอง
Josh

1
source somefileจะแตกต่างจากกระบวนการใหม่ถูกง่ามปิดโดย./somefileแม้ว่า
thrig

@thrig ใช่ฉันเห็นด้วย แต่ฉันไม่คิดว่า./somefileมันจะทำให้ bash รันคำสั่งsomefileหากไฟล์ไม่มีหมายเลขเวทย์มนตร์ ฉันคิดว่ามันจะแสดงข้อผิดพลาดและดูเหมือนจะมีประสิทธิภาพแทนsource somefile
Josh

ฉันผิดอีกครั้งฉันสามารถยืนยันได้ว่าหากsomefileเป็นไฟล์ข้อความจากนั้นเชลล์ใหม่จะเกิดขึ้นถ้าฉันพยายามเรียกใช้งาน ไฟล์echo $$ทำงานแตกต่างกันหากฉันเรียกใช้ vs source มัน
Josh

คำตอบ:


31

คำตอบที่ชัดเจนเพื่อ "วิธีการที่โปรแกรมได้รับการเรียกใช้" บน Linux เป็นคู่ของบทความเกี่ยวกับLWN.netเรื่องที่น่าแปลกใจพอวิธีโปรแกรมได้รับการเรียกใช้และวิธีการที่โปรแกรมได้รับการเรียกใช้: ไบนารีเอลฟ์ บทความแรกกล่าวถึงสคริปต์สั้น ๆ (การพูดอย่างเข้มงวดคำตอบที่ชัดเจนอยู่ในซอร์สโค้ด แต่บทความเหล่านี้ง่ายต่อการอ่านและให้ลิงค์ไปยังซอร์สโค้ด)

การทดลองเล็ก ๆ น้อย ๆ แสดงให้เห็นว่าคุณเข้าใจถูกต้องแล้วและการเรียกใช้ไฟล์ที่มีรายการคำสั่งง่ายๆโดยไม่ต้องมี Shebang นั้นจำเป็นต้องได้รับการจัดการโดยเชลล์ execve (2) manpage มีรหัสที่มาสำหรับโปรแกรมการทดสอบ execve; เราจะใช้มันเพื่อดูว่าเกิดอะไรขึ้นโดยไม่มีเปลือก ก่อนอื่นให้เขียน testcript testscr1ประกอบด้วย

#!/bin/sh

pstree

และอีกอันหนึ่งtestscr2มีเพียง

pstree

ทำให้ทั้งคู่สามารถเรียกใช้งานได้และตรวจสอบว่าทั้งคู่เรียกใช้จากเชลล์:

chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less

ตอนนี้ลองอีกครั้งโดยใช้execve(สมมติว่าคุณสร้างขึ้นในไดเรกทอรีปัจจุบัน):

./execve ./testscr1
./execve ./testscr2

testscr1ยังคงทำงาน แต่testscr2สร้าง

execve: Exec format error

นี่แสดงว่าเชลล์จัดการtestscr2แตกต่างกัน มันไม่ได้ประมวลผลสคริปต์เอง แต่มันก็ยังใช้/bin/shในการทำเช่นนั้น สิ่งนี้สามารถตรวจสอบได้โดยtestscr2ไปที่less:

./testscr2 | less -ppstree

ในระบบของฉันฉันได้รับ

    |-gnome-terminal--+-4*[zsh]
    |                 |-zsh-+-less
    |                 |     `-sh---pstree

อย่างที่คุณเห็นมีเชลล์ที่ฉันใช้zshซึ่งเริ่มต้นlessและเชลล์ตัวที่สองธรรมดาsh( dashในระบบของฉัน) เพื่อรันสคริปต์ซึ่งรันpstreeอยู่ ในzshสิ่งนี้ถูกจัดการโดยzexecveในSrc/exec.c: เชลล์ใช้execve(2)เพื่อพยายามรันคำสั่งและหากล้มเหลวมันจะอ่านไฟล์เพื่อดูว่ามันมี Shebang หรือไม่และประมวลผลตามนั้น (ซึ่งเคอร์เนลจะทำเช่นนั้น) และหาก ล้มเหลวมันพยายามที่จะเรียกใช้ไฟล์ด้วยshตราบใดที่มันไม่ได้อ่านศูนย์ไบต์ใด ๆ จากไฟล์:

        for (t0 = 0; t0 != ct; t0++)
            if (!execvebuf[t0])
                break;
        if (t0 == ct) {
            argv[-1] = "sh";
            winch_unblock();
            execve("/bin/sh", argv - 1, newenvp);
        }

bashมีพฤติกรรมเดียวกันนำไปใช้execute_cmd.cกับความคิดเห็นที่เป็นประโยชน์ (ดังที่ได้กล่าวไว้โดยtaliezin ):

ดำเนินการคำสั่งง่ายๆที่หวังว่าจะกำหนดไว้ในดิสก์ไฟล์ที่ใดที่หนึ่ง

  1. fork ()
  2. เชื่อมต่อท่อ
  3. ค้นหาคำสั่ง
  4. ทำการเปลี่ยนเส้นทาง
  5. execve ()
  6. หากexecveล้มเหลวให้ดูว่าไฟล์นั้นได้ตั้งค่าโหมดปฏิบัติการไว้หรือไม่ ถ้าเป็นเช่นนั้นและไม่ใช่ไดเรกทอรีให้เรียกใช้เนื้อหาเป็นเชลล์สคริปต์

POSIX กำหนดชุดของฟังก์ชั่นเป็นที่รู้จักฟังก์ชั่นซึ่งห่อและให้การทำงานนี้มากเกินไป; ดูคำตอบของmuruสำหรับรายละเอียด อย่างน้อยที่สุดฟังก์ชั่นเหล่านี้ถูกใช้งานโดยไลบรารี่ C ไม่ใช่โดยเคอร์เนลexec(3)execve(2)


นี่เป็นสิ่งที่ยอดเยี่ยมและมีรายละเอียดที่ฉันต้องการขอขอบคุณ!
Josh

12

ในส่วนนี้ขึ้นอยู่กับexecฟังก์ชันตระกูลที่ใช้ execveตามที่สตีเฟ่นคิตแสดงในรายละเอียดเพียงรันไฟล์ในรูปแบบไบนารีที่ถูกต้องหรือสคริปต์ที่ขึ้นต้นด้วย shebang ที่เหมาะสม

อย่างไรก็ตาม , execlpและexecvpไปหนึ่งก้าว: ถ้า shebang ก็ไม่ถูกต้องไฟล์จะถูกดำเนินการด้วย/bin/shบน Linux จากman 3 exec:

Special semantics for execlp() and execvp()
   The execlp(), execvp(), and execvpe() functions duplicate the actions
   of the shell in searching for an executable file if the specified
   filename does not contain a slash (/) character.
   …

   If the header of a file isn't recognized (the attempted execve(2)
   failed with the error ENOEXEC), these functions will execute the
   shell (/bin/sh) with the path of the file as its first argument.  (If
   this attempt fails, no further searching is done.)

สิ่งนี้ได้รับการสนับสนุนโดยPOSIX (เหมืองที่เน้น):

แหล่งที่มาของความสับสนที่อาจเกิดขึ้นซึ่งผู้พัฒนามาตรฐานระบุไว้คือเนื้อหาของไฟล์อิมเมจกระบวนการส่งผลกระทบต่อพฤติกรรมของฟังก์ชันตระกูล exec ต่อไปนี้เป็นคำอธิบายของการกระทำที่ทำ:

  1. หากไฟล์อิมเมจกระบวนการเป็นไฟล์เรียกทำงานที่ถูกต้อง (ในรูปแบบที่สามารถใช้งานได้และถูกต้องและมีสิทธิ์ที่เหมาะสม) สำหรับระบบนี้ระบบจะเรียกใช้ไฟล์

  2. หากไฟล์อิมเมจกระบวนการมีสิทธิ์ที่เหมาะสมและอยู่ในรูปแบบที่สามารถใช้งานได้ แต่ไม่ถูกต้องสำหรับระบบนี้ (เช่นไบนารีที่รู้จักสำหรับสถาปัตยกรรมอื่น) แสดงว่าเป็นข้อผิดพลาดและ errno ตั้งค่าเป็น [EINVAL] (ดูภายหลัง บน [EINVAL])

  3. หากไฟล์อิมเมจกระบวนการมีสิทธิ์ที่เหมาะสม แต่ไม่ได้รับการยอมรับ:

    1. หากนี่เป็นการเรียกไปยัง execlp () หรือ execvp () จากนั้นพวกเขาก็เรียกล่ามคำสั่งสมมติว่าไฟล์อิมเมจกระบวนการเป็นเชลล์สคริปต์

    2. หากนี่ไม่ใช่การเรียกไปที่ execlp () หรือ execvp () แสดงว่ามีข้อผิดพลาดเกิดขึ้นและ errno ถูกตั้งค่าเป็น [ENOEXEC]

นี่ไม่ได้ระบุว่าจะรับล่ามคำสั่งได้อย่างไร แต่ไม่ได้ระบุว่าจะต้องมีข้อผิดพลาด ฉันเดาว่าผู้พัฒนา Linux อนุญาตให้เรียกใช้ไฟล์ดังกล่าวได้/bin/sh(หรือนี่เป็นวิธีปฏิบัติทั่วไปแล้ว

FWIW, FreeBSD manpage สำหรับexec(3)กล่าวถึงพฤติกรรมที่คล้ายกัน:

 Some of these functions have special semantics.

 The functions execlp(), execvp(), and execvP() will duplicate the actions
 of the shell in searching for an executable file if the specified file
 name does not contain a slash ``/'' character. 
 …
 If the header of a file is not recognized (the attempted execve()
 returned ENOEXEC), these functions will execute the shell with the path
 of the file as its first argument.  (If this attempt fails, no further
 searching is done.)

อย่างไรก็ตาม AFAICT ไม่มีเชลล์ทั่วไปที่ใช้execlpหรือexecvpโดยตรงเพื่อสันนิษฐานว่าสามารถควบคุมสภาพแวดล้อมได้ดีกว่า execveพวกเขาทุกคนใช้ตรรกะเดียวกันโดยใช้


3
ฉันยังต้องการเพิ่มว่าอย่างน้อยในลินุกซ์execl, execlp, execle, execv, execvpและexecvpeทั้งหมดที่ปลายด้านหน้าไปexecvesyscall; อดีตจัดทำโดย C ไลบรารีเคอร์เนลรู้เท่านั้นexecve(และexecveatทุกวันนี้)
Stephen Kitt

@StephenKitt นั่นอธิบายว่าทำไมฉันไม่สามารถหา manpage สำหรับฟังก์ชั่นเหล่านั้นได้ในส่วนของ man7.org 2
muru

6

นี่อาจเป็นคำตอบเพิ่มเติมของ Stephen Kitt ในฐานะที่เป็นความคิดเห็นจากbashแหล่งที่มาในไฟล์execute_cmd.c:

ดำเนินการคำสั่งง่ายๆที่หวังว่าจะกำหนดไว้ในดิสก์ไฟล์ที่ใดที่หนึ่ง

1. fork ()
2. connect pipes
3. look up the command
4. do redirections
5. execve ()
6. If the execve failed, see if the file has executable mode set.  

ถ้าเป็นเช่นนั้นและไม่ใช่ไดเรกทอรีให้เรียกใช้เนื้อหาเป็นเชลล์สคริปต์


0

มันจะถูกเรียกใช้งานเป็นเชลล์สคริปต์ซึ่งไม่ได้เป็นแหล่งที่มา (ตัวอย่างเช่นตัวแปรที่ตั้งค่าในไฟล์ที่เรียกใช้งานจะไม่มีผลกับภายนอก) อาจร่องรอยจากอดีตที่ผ่านมามีหมอกเมื่อมีหนึ่งเปลือกและรูปแบบที่ปฏิบัติการหนึ่ง ไม่ใช่ไฟล์ที่เรียกใช้งานได้จะต้องเป็นเชลล์สคริปต์


2
คุณเข้าใจคำถามของฉันผิด รายละเอียดเกิดอะไรขึ้น อย่างน้อยที่สุดฉันต้องเข้าใจว่าการตรวจสอบสำหรับ Shebang นั้นคือexec()อะไรหรือไม่? ฉันต้องการ internals มากขึ้นอย่างมาก
Josh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.