ฉันจะฆ่ากระบวนการได้อย่างไรและตรวจสอบว่า PID ไม่ได้ถูกนำมาใช้ซ้ำ


40

ตัวอย่างเช่นสมมติว่าคุณมีเชลล์สคริปต์คล้ายกับ:

longrunningthing &
p=$!
echo Killing longrunningthing on PID $p in 24 hours
sleep 86400
echo Time up!
kill $p

ควรทำเคล็ดลับไม่ได้หรือไม่ ยกเว้นว่ากระบวนการนี้อาจสิ้นสุดก่อนกำหนดและ PID ของมันอาจถูกนำกลับมาใช้ใหม่หมายถึงงานที่ไร้เดียงสาบางอย่างได้รับระเบิดในคิวสัญญาณแทน ในทางปฏิบัติสิ่งนี้อาจมีความสำคัญ แต่มันทำให้ฉันกังวล การแฮ็กข้อมูลนาน ๆ เพื่อที่จะตายด้วยตัวเองหรือเก็บ / ลบ PID ของมันบน FS จะทำ แต่ฉันคิดถึงสถานการณ์ทั่วไปที่นี่


3
คุณตรวจสอบให้แน่ใจว่าหากกระบวนการเป้าหมายของคุณตายไปแล้วมันจะฆ่าฆาตกรในกระบวนการ
mikeserv

2
การใช้งานที่ตรงกับชื่อดังนั้นอย่างน้อยคุณเป็นเพียงการฆ่ากระบวนการที่มีชื่อเดียวกับkillall longrunningthingสมมติว่าคุณจะมีหนึ่งในการทำงานเหล่านี้ในแต่ละครั้ง
LawrenceC

8
คุณสามารถบันทึกเวลาเริ่มต้นของกระบวนการดั้งเดิมและก่อนที่จะฆ่าตรวจสอบว่าเวลาเริ่มต้นของกระบวนการด้วย pid นั้นตรงกับสิ่งที่คุณบันทึกไว้ pair pid, start-time เป็นตัวบ่งชี้เฉพาะสำหรับกระบวนการใน Linux
บากูริว

1
ฉันขอถามได้ไหมว่าทำไมคุณถึงต้องการสิ่งนี้ สิ่งพื้นฐานที่คุณพยายามทำคืออะไร (สิ่งที่ทำงานอย่างต่อเนื่อง แต่จะถูกลบทุก 24 ชั่วโมง?)
Olivier Dulac

2
@mikeserv กระบวนการไม่สามารถรับประกันได้ว่าบางสิ่งจะเกิดขึ้นในกรณีที่มีการตายของตัวเอง
kasperd

คำตอบ:


29

ดีที่สุดคือการใช้timeoutคำสั่งถ้าคุณมีมันซึ่งมีความหมายสำหรับที่:

timeout 86400 cmd

การใช้งาน GNU ปัจจุบัน (8.23) อย่างน้อยก็ใช้งานได้โดยใช้alarm()หรือเทียบเท่าในขณะที่รอกระบวนการลูก ดูเหมือนจะไม่ได้รับการปกป้องจากการSIGALRMถูกส่งระหว่างการwaitpid()กลับมาและการtimeoutออก (การยกเลิกการเตือนภัยอย่างมีประสิทธิภาพ) ในช่วงหน้าต่างเล็ก ๆ นั้นtimeoutอาจเขียนข้อความบน stderr (เช่นถ้าเด็กทิ้งแกน) ซึ่งจะขยายหน้าต่างการแข่งขันต่อไป (โดยไม่มีกำหนดหาก stderr เป็นไพพ์เต็มเป็นต้น)

ฉันเองสามารถอยู่กับข้อ จำกัด นั้น (ซึ่งอาจจะได้รับการแก้ไขในรุ่นอนาคต) timeoutจะต้องระมัดระวังเป็นพิเศษในการรายงานสถานะการออกที่ถูกต้องจัดการกับกรณีมุมอื่น ๆ (เช่น SIGALRM ถูกบล็อก / เพิกเฉยเมื่อเริ่มต้นจัดการสัญญาณอื่น ๆ ... ) ดีกว่าที่คุณอาจจัดการด้วยมือ

คุณสามารถเขียนเป็นperlดังนี้:

perl -MPOSIX -e '
  $p = fork();
  die "fork: $!\n" unless defined($p);
  if ($p) {
    $SIG{ALRM} = sub {
      kill "TERM", $p;
      exit 124;
    };
    alarm(86400);
    wait;
    exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
  } else {exec @ARGV}' cmd

มีtimelimitคำสั่งที่http://devel.ringlet.net/sysutils/timelimit/ (ถือกำเนิด GNU timeoutภายในไม่กี่เดือน)

 timelimit -t 86400 cmd

อันนั้นใช้alarm()กลไกคล้ายกัน แต่ติดตั้งตัวจัดการSIGCHLD(ไม่สนใจลูกหยุด) เพื่อตรวจจับเด็กที่กำลังจะตาย นอกจากนี้ยังยกเลิกการเตือนก่อนที่จะทำงานwaitpid()(ที่ไม่ได้ยกเลิกการส่งมอบSIGALRMถ้ามันอยู่ระหว่างดำเนินการ แต่วิธีการเขียนฉันไม่เห็นว่ามันเป็นปัญหา) และฆ่าก่อนที่จะโทรwaitpid()(ดังนั้นไม่สามารถฆ่า pid ที่นำมาใช้ใหม่ )

netpipesยังมีtimelimitคำสั่ง อันนั้นมีมาก่อนอีกอันหนึ่งทุกสิบปีใช้วิธีการอื่น แต่ไม่สามารถทำงานได้อย่างถูกต้องสำหรับคำสั่งหยุดและคืน1สถานะทางออกเมื่อหมดเวลา

เพื่อเป็นการตอบคำถามของคุณโดยตรงคุณสามารถทำสิ่งต่อไปนี้

if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
  kill "$p"
fi

นั่นคือตรวจสอบว่ากระบวนการยังคงเป็นลูกของเรา อีกครั้งมีหน้าต่างการแข่งขันขนาดเล็ก (ในระหว่างpsการรับสถานะของกระบวนการและkillฆ่ามัน) ในระหว่างที่กระบวนการอาจตายและ pid ของมันจะถูกนำกลับมาใช้โดยกระบวนการอื่น

ด้วยเปลือกหอยบางคน ( zsh, bash, mksh) คุณสามารถส่งรายละเอียดงานแทน PIDs

cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status

สิ่งนี้ใช้ได้ผลก็ต่อเมื่อคุณวางไข่งานแบ็คกราวนด์เพียงงานเดียวเท่านั้น

หากเป็นปัญหาให้เริ่มต้นอินสแตนซ์เชลล์ใหม่:

bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd

ใช้งานได้เพราะเชลล์ลบงานออกจากตารางงานเมื่อเด็กกำลังจะตาย ที่นี่ไม่ควรมีหน้าต่างการแข่งขันใด ๆ ตั้งแต่เวลาที่เชลล์เรียกkill()สัญญาณ SIGCHLD ไม่ได้รับการจัดการและ pid ไม่สามารถนำกลับมาใช้ใหม่ได้ (เนื่องจากไม่ได้รับการรอคอย) หรือถูกจัดการและ งานถูกลบออกจากตารางกระบวนการ (และkillจะรายงานข้อผิดพลาด) bash's killที่บล็อกน้อย SIGCHLD ก่อนที่จะเข้าถึงตารางงานของตนที่จะขยาย%และ unblocks kill()มันหลังจากที่

ตัวเลือกอื่นเพื่อหลีกเลี่ยงsleepกระบวนการที่แขวนอยู่รอบ ๆ แม้cmdจะตายไปแล้วด้วยbashหรือksh93จะใช้ไพพ์read -tแทนsleep:

{
  {
    cmd 4>&1 >&3 3>&- &
    printf '%d\n.' "$!"
  } | {
    read p
    read -t 86400 || kill "$p"
  }
} 3>&1

อันนั้นยังคงมีสภาพการแข่งขันและคุณสูญเสียสถานะการออกคำสั่ง และยังถือว่าcmdไม่ปิด fd 4

คุณสามารถลองใช้โซลูชันที่ปราศจากการแข่งขันได้perlเช่น:

perl -MPOSIX -e '
   $p = fork();
   die "fork: $!\n" unless defined($p);
   if ($p) {
     $SIG{CHLD} = sub {
       $ss = POSIX::SigSet->new(SIGALRM); $oss = POSIX::SigSet->new;
       sigprocmask(SIG_BLOCK, $ss, $oss);
       waitpid($p,WNOHANG);
       exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
           unless $? == -1;
       sigprocmask(SIG_UNBLOCK, $oss);
     };
     $SIG{ALRM} = sub {
       kill "TERM", $p;
       exit 124;
     };
     alarm(86400);
     pause while 1;
   } else {exec @ARGV}' cmd args...

(แม้ว่าจะต้องมีการปรับปรุงเพื่อรองรับกรณีมุมประเภทอื่น)

อีกวิธีที่ปราศจากการแข่งขันสามารถใช้กลุ่มกระบวนการ:

set -m
((sleep 86400; kill 0) & exec cmd)

อย่างไรก็ตามโปรดทราบว่าการใช้กลุ่มกระบวนการสามารถมีผลข้างเคียงหากมี I / O กับอุปกรณ์ปลายทางที่เกี่ยวข้อง มันมีประโยชน์เพิ่มเติมแม้ว่าจะฆ่าทุกกระบวนการพิเศษอื่น ๆ cmdกลับกลายโดย


4
ทำไมไม่พูดถึงวิธีที่ดีที่สุดก่อน?
deltab

2
@deltab: timeoutไม่ใช่แบบพกพาคำตอบที่กล่าวถึงโซลูชันแบบพกพาก่อน
cuonglm

1
@deltab: มันให้ข้อมูลเชิงลึกเกี่ยวกับวิธีการทำงานของสิ่งต่าง ๆ และโดยเฉพาะอย่างยิ่งวิธีการที่ "สามัญสำนึก" วิธีการล้มเหลว หนึ่งคาดว่าจะอ่านคำตอบทั้งหมด
Olivier Dulac

@Stephane: สำหรับ "การหา jobpec ที่ถูกต้องนั้นเป็นไปไม่ได้เสมอที่เชื่อถือได้": คุณไม่สามารถนับเอาท์พุทของjobsแล้วรู้ว่า (เพราะมันเป็นเชลล์ของคุณเองซึ่งคุณสามารถควบคุมสิ่งที่เกิดขึ้นต่อไปได้) งานจะเป็น N + 1? [จากนั้นคุณสามารถบันทึก N และหลังจากนั้นฆ่า% N + 1])
Olivier Dulac

1
@OlivierDulac ที่จะถือว่าไม่มีงานที่ผ่านมาถูกยกเลิกโดยเวลาที่คุณเริ่มงานใหม่ (เชลล์นำหมายเลขงานกลับมาใช้ใหม่)
Stéphane Chazelas

28

โดยทั่วไปแล้วคุณไม่สามารถ คำตอบทั้งหมดที่ให้ในตอนนี้คือการวิเคราะห์พฤติกรรมบั๊กกี้ มีเพียงกรณีเดียวที่คุณสามารถใช้ pid เพื่อส่งสัญญาณได้อย่างปลอดภัย: เมื่อกระบวนการเป้าหมายเป็นลูกโดยตรงของกระบวนการที่จะส่งสัญญาณและผู้ปกครองยังไม่ได้รอ ในกรณีนี้แม้ว่าจะออกแล้ว pid ถูกสงวนไว้ (นี่คือสิ่งที่ "กระบวนการซอมบี้") จนกว่าผู้ปกครองจะรอ ฉันไม่ได้ตระหนักถึงวิธีที่จะทำอย่างนั้นกับเปลือก

ทางเลือกที่ปลอดภัยอีกทางหนึ่งในการฆ่าโปรเซสคือเริ่มต้นด้วยชุดควบคุม tty เป็นเทอร์มินัลเทียมที่คุณเป็นเจ้าของฝั่งมาสเตอร์ จากนั้นคุณสามารถส่งสัญญาณผ่านเทอร์มินัลเช่นการเขียนตัวอักษรสำหรับSIGTERMหรือSIGQUITมากกว่า pty

อีกวิธีที่สะดวกกว่าในการเขียนสคริปต์คือใช้screenเซสชันที่มีชื่อและส่งคำสั่งไปยังเซสชันหน้าจอเพื่อสิ้นสุด กระบวนการนี้เกิดขึ้นเหนือไพพ์หรือซ็อกเก็ตยูนิกซ์ที่ตั้งชื่อตามเซสชันหน้าจอซึ่งจะไม่ถูกนำมาใช้ซ้ำโดยอัตโนมัติหากคุณเลือกชื่อที่ไม่ซ้ำที่ปลอดภัย


4
ฉันไม่เห็นสาเหตุที่ทำไม่ได้ในกระสุน ฉันได้รับการแก้ปัญหาหลายอย่าง
Stéphane Chazelas

3
คุณช่วยอธิบายและอภิปรายเชิงปริมาณของหน้าต่างการแข่งขันและข้อเสียอื่น ๆ ได้ไหม หากปราศจากคำว่า"คำตอบทั้งหมดที่ให้ไว้ในตอนนี้คือการวิเคราะห์พฤติกรรมบั๊กกี้"เป็นการเผชิญหน้าที่ไม่จำเป็นโดยไม่มีประโยชน์ใด ๆ
เตอร์

3
@ peterph: โดยทั่วไปแล้วการใช้ pid ใด ๆ เป็นการแข่งขัน TOCTOU - ไม่ว่าคุณจะตรวจสอบอย่างไรว่ามันยังอ้างอิงถึงกระบวนการเดียวกันกับที่คุณคาดหวังให้อ้างถึงมันสามารถหยุดอ้างถึงกระบวนการนั้นและอ้างอิงถึงสิ่งใหม่ ดำเนินการในช่วงเวลาก่อนที่คุณจะใช้มัน (ส่งสัญญาณ) วิธีเดียวที่จะป้องกันไม่ให้สิ่งนี้คือการสามารถป้องกันการเพิ่ม / การใช้ซ้ำของ pid และกระบวนการเดียวที่สามารถทำได้คือผู้ปกครองโดยตรง
. ..

2
@ StéphaneChazelas: คุณจะป้องกันเชลล์จากการรอ pid ของกระบวนการพื้นหลังที่ออกได้อย่างไร หากคุณสามารถทำเช่นนั้นปัญหาจะสามารถแก้ไขได้ในกรณีที่ OP ต้องการ
..

5
@ peterph: "หน้าต่างการแข่งขันมีขนาดเล็ก" ไม่ใช่วิธีแก้ปัญหา และความหายากของการแข่งขันขึ้นอยู่กับการมอบหมาย pid ตามลำดับ ข้อบกพร่องที่ทำให้สิ่งเลวร้ายเกิดขึ้นปีละครั้งจะแย่กว่าข้อบกพร่องที่เกิดขึ้นตลอดเวลาเพราะไม่สามารถวินิจฉัยและแก้ไขได้
..

10
  1. เมื่อเรียกใช้กระบวนการประหยัดเวลาเริ่มต้น:

    longrunningthing &
    p=$!
    stime=$(TZ=UTC0 ps -p "$p" -o lstart=)
    
    echo "Killing longrunningthing on PID $p in 24 hours"
    sleep 86400
    echo Time up!
    
  2. ก่อนที่จะพยายามฆ่ากระบวนการให้หยุด (สิ่งนี้ไม่จำเป็นจริงๆ แต่เป็นวิธีหลีกเลี่ยงสภาวะการแข่งขัน: ถ้าคุณหยุดกระบวนการก็จะไม่สามารถนำ pid กลับมาใช้ใหม่ได้)

    kill -s STOP "$p"
    
  3. ตรวจสอบว่ากระบวนการที่ใช้ PID นั้นมีเวลาเริ่มต้นที่เท่ากันและถ้าใช่ให้ปิดใช้งานมิฉะนั้นให้กระบวนการดำเนินการต่อไป:

    cur=$(TZ=UTC0 ps -p "$p" -o lstart=)
    
    if [ "$cur" = "$stime" ]
    then
        # Okay, we can kill that process
        kill "$p"
    else
        # PID was reused. Better unblock the process!
        echo "long running task already completed!"
        kill -s CONT "$p"
    fi
    

ใช้งานได้เนื่องจากมีเพียงกระบวนการเดียวที่มี PID เดียวกันและเวลาเริ่มต้นในระบบปฏิบัติการที่กำหนด

การหยุดกระบวนการในระหว่างการตรวจสอบทำให้สภาพการแข่งขันไม่ใช่ปัญหา เห็นได้ชัดว่านี่เป็นปัญหาที่เกิดขึ้นกระบวนการสุ่มบางอย่างอาจหยุดลงเป็นมิลลิวินาที ขึ้นอยู่กับประเภทของกระบวนการนี้อาจเป็นหรือไม่มีปัญหาก็ได้


โดยส่วนตัวแล้วฉันใช้ python และpsutilจัดการ PID ที่ใช้ซ้ำโดยอัตโนมัติ:

import time

import psutil

# note: it would be better if you were able to avoid using
#       shell=True here.
proc = psutil.Process('longrunningtask', shell=True)
time.sleep(86400)

# PID reuse handled by the library, no need to worry.
proc.terminate()   # or: proc.kill()

Python rules ใน UNIX ... ฉันไม่แน่ใจว่าทำไมคำตอบเพิ่มเติมไม่เริ่มต้นที่นั่นเพราะฉันมั่นใจว่าระบบส่วนใหญ่ไม่ห้ามการใช้งาน
Mr. Mascaro

ฉันเคยใช้ชุดรูปแบบที่คล้ายกัน (ใช้เวลาเริ่มต้น) ก่อนหน้านี้ แต่ทักษะการเขียนสคริปต์ของคุณดีกว่าของฉัน! ขอบคุณ
FJL

นั่นหมายความว่าคุณอาจหยุดกระบวนการที่ไม่ถูกต้อง โปรดทราบว่าps -o start=รูปแบบนั้นเปลี่ยนจาก 18:12 ถึง Jan26 หลังจากนั้นไม่นาน ระวังการเปลี่ยนแปลง DST เช่นกัน ถ้าบน Linux TZ=UTC0 ps -o lstart=คุณอาจจะชอบ
Stéphane Chazelas

@ StéphaneChazelasใช่ แต่คุณปล่อยให้มันดำเนินต่อไปในภายหลัง ฉันพูดอย่างชัดเจน: ขึ้นอยู่กับประเภทของงานที่กระบวนการนั้นกำลังทำงานอยู่คุณอาจมีปัญหาในการหยุดกระบวนการบางมิลลิวินาที ขอบคุณสำหรับเคล็ดลับที่lstartฉันจะแก้ไขมันมา
บาคุริ

โปรดทราบว่า (เว้นแต่ระบบของคุณจะ จำกัด จำนวนกระบวนการต่อผู้ใช้) เป็นเรื่องง่ายสำหรับทุกคนที่จะเติมตารางกระบวนการด้วยซอมบี้ เมื่อเหลือเพียง 3 pids ที่เหลืออยู่มันเป็นเรื่องง่ายสำหรับทุกคนที่จะเริ่มต้นกระบวนการที่แตกต่างกันนับร้อยด้วย pid เดียวกันภายในหนึ่งวินาที ดังนั้นการพูดอย่างเคร่งครัด"ของคุณอาจมีเพียงกระบวนการเดียวที่มี PID เดียวกันและเวลาเริ่มต้นในระบบปฏิบัติการที่กำหนด"ไม่จำเป็นต้องเป็นจริง
Stéphane Chazelas

7

บนระบบ linux คุณสามารถมั่นใจได้ว่า pid จะไม่ถูกนำมาใช้ซ้ำโดยรักษา pid namespace ให้มีชีวิตอยู่ สามารถทำได้ผ่าน/proc/$pid/ns/pidไฟล์

  • man namespaces -

    ผูกการติดตั้ง(ดูmount(2))หนึ่งในไฟล์ในไดเรกทอรีนี้ไปยังที่อื่นในระบบแฟ้มเก็บ namespace ที่สอดคล้องกันของกระบวนการที่ระบุโดย pid ชีวิตแม้ว่ากระบวนการทั้งหมดในปัจจุบันยุติ namespace

    การเปิดไฟล์ใดไฟล์หนึ่งในไดเรกทอรีนี้(หรือไฟล์ที่เชื่อมโยงกับหนึ่งในไฟล์เหล่านี้)จะส่งคืนการจัดการไฟล์สำหรับเนมสเปซที่สอดคล้องกันของกระบวนการที่ระบุโดย pid ตราบใดที่ตัวให้คำอธิบายไฟล์นี้ยังคงเปิดอยู่เนมสเปซจะยังคงอยู่แม้ว่ากระบวนการทั้งหมดในเนมสเปซจะยุติลง ไฟล์ descriptor สามารถส่งผ่านไปsetns(2)ได้

คุณสามารถแยกกลุ่มของกระบวนการ - พื้นจำนวนของกระบวนการใด ๆ - โดย namespacing initของพวกเขา

  • man pid_namespaces -

    กระบวนการแรกที่สร้างในเนมสเปซใหม่(เช่นกระบวนการที่สร้างโดยใช้clone(2) กับธงCLONE_NEWPIDหรือชายด์แรกที่สร้างโดยกระบวนการหลังจากการเรียกunshare(2)ใช้ แฟล็ก CLONE_NEWPID )มีPID 1และเป็นinitกระบวนการสำหรับเนมสเปซ( ดูinit(1) ) กระบวนการเด็กที่กำพร้าภายใน namespace จะ reparented กับกระบวนการนี้มากกว่าinit(1) (ยกเว้นกรณีที่หนึ่งของบรรพบุรุษของเด็กในเดียวกันPID namespace ลูกจ้างprctl(2) PR_SET_CHILD_SUBREAPERคำสั่งเพื่อทำเครื่องหมายตัวเองเป็นคนที่เกี่ยวของกระบวนการลูกหลานกำพร้า)

    หากinitกระบวนการของPIDเนมสเปซสิ้นสุดลงเคอร์เนลจะยุติกระบวนการทั้งหมดในเนมสเปซผ่าน สัญญาณSIGKILL พฤติกรรมนี้สะท้อนถึงความจริงที่ว่าinitกระบวนการเป็นสิ่งจำเป็นสำหรับการดำเนินการที่ถูกต้องของPIDเนมสเปซ

util-linuxแพคเกจมีเครื่องมือที่มีประโยชน์มากมายสำหรับการจัดการ namespaces ตัวอย่างเช่นมีunshareแม้ว่าหากคุณยังไม่ได้จัดเรียงสิทธิ์ในเนมสเปซผู้ใช้จะต้องใช้สิทธิ์ superuser:

unshare -fp sh -c 'n=
    echo "PID = $$"
    until   [ "$((n+=1))" -gt 5 ]
    do      while   sleep 1
            do      date
            done    >>log 2>/dev/null   &
    done;   sleep 5' >log
cat log; sleep 2
echo 2 secs later...
tail -n1 log

หากคุณไม่ได้จัดเรียงสำหรับเนมสเปซของผู้ใช้คุณจะสามารถดำเนินการคำสั่งตามอำเภอใจได้อย่างปลอดภัยโดยการยกเลิกสิทธิ์ทันที runuserคำสั่งเป็นอีกหนึ่ง(ไม่ใช่ setuid)ไบนารีให้บริการโดยutil-linuxแพคเกจและการใช้มาตรการที่มันอาจจะมีลักษณะดังนี้:

sudo unshare -fp runuser -u "$USER" -- sh -c '...'

... และต่อไป

ในตัวอย่างข้างต้นสวิตช์สองตัวถูกส่งผ่านไปunshare(1)ยัง--forkแฟล็กซึ่งทำให้sh -cกระบวนการที่เรียกใช้เป็นชายด์แรกที่สร้างและตรวจสอบinitสถานะและ--pidแฟล็กที่แนะนำunshare(1)ให้สร้างเนมสเปซ pid

sh -cspawns กระบวนการห้า backgrounded หอยเด็ก - แต่ละ inifinite whileห่วงที่จะยังคงที่จะผนวกการส่งออกของdateไปยังจุดสิ้นสุดของlogให้นานที่สุดเท่าsleep 1ผลตอบแทนที่แท้จริง หลังจากวางไข่กระบวนการเหล่านี้shเรียกร้องsleepให้เพิ่มอีก 5 วินาทีแล้วยุติ

มันอาจคุ้มค่าที่จะสังเกตว่าหาก-fธงไม่ได้ใช้งานจะไม่มีwhileลูปที่มีพื้นหลังเกิดขึ้น แต่จะ ...

เอาท์พุท:

PID = 1
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:45 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:46 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:47 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
Mon Jan 26 19:17:48 PST 2015
2 secs later...
Mon Jan 26 19:17:48 PST 2015

คำตอบที่น่าสนใจที่ดูเหมือนจะแข็งแกร่ง อาจ overkill เล็กน้อยสำหรับการใช้งานขั้นพื้นฐาน แต่ควรค่ากับความคิด
Uriel

ฉันไม่เห็นว่าทำไมการรักษาเนมสเปซ PID จึงมีผลต่อการป้องกันการใช้ PID ซ้ำ manpage ที่คุณอ้างถึง - ตราบใดที่ตัวให้คำอธิบายไฟล์นี้ยังคงเปิดอยู่เนมสเปซจะยังคงอยู่แม้ว่ากระบวนการทั้งหมดในเนมสเปซจะยุติ - แสดงว่ากระบวนการอาจยังคงยุติลงได้ อะไรที่ทำให้ namespace PID ยังมีชีวิตอยู่ต้องทำอย่างไรเพื่อป้องกัน PID เองไม่ให้ถูกใช้ซ้ำโดยกระบวนการอื่น?
davmac

5

พิจารณาทำให้longrunningthingพฤติกรรมของคุณดีขึ้นเล็กน้อยเหมือนดีมอนขึ้นเล็กน้อย ตัวอย่างเช่นคุณอาจทำให้มันสร้างpidfileที่จะช่วยให้การควบคุมกระบวนการ จำกัด อย่างน้อย มีหลายวิธีในการทำเช่นนี้โดยไม่ต้องแก้ไขไบนารีต้นฉบับทั้งหมดที่เกี่ยวข้องกับเสื้อคลุม ตัวอย่างเช่น:

  1. สคริปต์ตัวห่อหุ้มอย่างง่ายที่จะเริ่มงานที่ต้องการในพื้นหลัง (ด้วยการเปลี่ยนเส้นทางออกทางเลือก) เขียน PID ของกระบวนการนี้เป็นไฟล์จากนั้นรอให้กระบวนการเสร็จสิ้น (โดยใช้wait) และลบไฟล์ หากในระหว่างการรอกระบวนการถูกฆ่าเช่นบางสิ่งบางอย่างเช่น

    kill $(cat pidfile)
    

    wrapper จะตรวจสอบให้แน่ใจว่าได้ลบ pidfile แล้ว

  2. wrapper ของมอนิเตอร์ที่จะทำให้PID ของตัวเองอยู่ที่ไหนสักแห่งและจับสัญญาณ (และตอบสนองต่อ) ส่งไป ตัวอย่างง่ายๆ:

    #!/bin/bash
    p=0
    trap killit USR1

    killit () {
        printf "USR1 caught, killing %s\n" "$p"
        kill -9 $p
    }

    printf "monitor $$ is waiting\n"
    therealstuff &
    p=%1
    wait $p
    printf "monitor exiting\n"

ตอนนี้เมื่อ @R .. และ @ StéphaneChazelasชี้ให้เห็นว่าวิธีการเหล่านี้มักจะมีสภาพการแข่งขันที่ไหนสักแห่งหรือกำหนดข้อ จำกัด เกี่ยวกับจำนวนกระบวนการที่คุณสามารถวางไข่ได้ นอกจากนี้ยังไม่จัดการกรณีที่longrunningthingอาจแยกและเด็ก ๆ แยกออก (ซึ่งอาจไม่ใช่สิ่งที่เป็นปัญหาในคำถามเดิม)

ด้วยเคอร์เนลลีนุกซ์ล่าสุด (อ่านสองสามปี) นี้สามารถรักษาได้อย่างดีโดยใช้กลุ่มcgคือช่องแช่แข็ง - ซึ่งฉันคิดว่าเป็นสิ่งที่บางระบบ Linux ใช้ที่ทันสมัย


ขอขอบคุณและทุกคน ฉันกำลังอ่านทุกอย่างตอนนี้ประเด็นlongrunningthingก็คือคุณไม่สามารถควบคุมมันได้ ฉันยังให้ตัวอย่างเชลล์สคริปต์เพราะอธิบายปัญหา ฉันชอบของคุณและโซลูชันสร้างสรรค์อื่น ๆ ทั้งหมดที่นี่ แต่ถ้าคุณใช้ Linux / bash จะมี "หมดเวลา" ในตัว ฉันคิดว่าฉันควรจะได้รับแหล่งที่มาและดูว่ามันไม่!
FJL

@FJL, timeoutคือไม่ builtin เปลือก มีการใช้งานหลายอย่างของtimeoutคำสั่งสำหรับลีนุกซ์, อันหนึ่งเพิ่งเพิ่ม (2008) ไปยัง GNU coreutils (ไม่ใช่เฉพาะลีนุกซ์), และนั่นคือสิ่งที่ลีนุกซ์ส่วนใหญ่ใช้ในปัจจุบัน.
Stéphane Chazelas

@ Stéphane - ขอบคุณ - ฉันพบภายหลังการอ้างอิงถึง GNU coreutils มันอาจจะพกพาได้ แต่ถ้าไม่อยู่ในระบบพื้นฐานมันไม่สามารถไว้ใจได้ ฉันสนใจที่จะรู้ว่ามันทำงานอย่างไรแม้ว่าฉันจะทราบความคิดเห็นของคุณที่อื่นแนะนำว่ามันไม่น่าเชื่อถือ 100% ด้วยวิธีการที่หัวข้อนี้ได้หายไปฉันไม่แปลกใจ!
FJL

1

หากคุณกำลังรันบน Linux (และ * อื่น ๆ อีกไม่กี่) คุณสามารถตรวจสอบว่ากระบวนการที่คุณตั้งใจจะฆ่ายังคงใช้อยู่หรือไม่และบรรทัดคำสั่งนั้นตรงกับกระบวนการที่ยาวนานของคุณหรือไม่ สิ่งที่ต้องการ :

echo Time up!
grep -q longrunningthing /proc/$p/cmdline 2>/dev/null
if [ $? -eq 0 ]
then
  kill $p
fi

ps -p $p -o etime=เป็นทางเลือกที่สามารถที่จะตรวจสอบนานเท่าไหร่กระบวนการที่คุณตั้งใจจะฆ่ากำลังทำงานกับสิ่งที่ต้องการ คุณสามารถทำได้ด้วยตัวเองโดยการดึงข้อมูลนี้ออกมา/proc/$p/statแต่มันอาจเป็นเรื่องยุ่งยาก (เวลาจะถูกวัดในระยะเวลาอันสั้นและคุณจะต้องใช้เวลาทำงานของระบบ/proc/statด้วย)

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


ยังไม่ถูกต้องเพราะมันไม่ได้กำจัดสภาพการแข่งขัน
strcat

@strcat แน่นอนว่าไม่มีการรับประกันความสำเร็จ แต่สคริปต์ส่วนใหญ่ไม่สนใจที่จะทำการตรวจสอบดังกล่าวและฆ่าcat pidfileผลลัพธ์โดยไม่เจตนา ฉันไม่สามารถจำวิธีทำความสะอาดได้ในเชลล์เท่านั้น คำตอบ namespace ที่เสนอนั้นดูเหมือนว่าจะเป็นการขัดจังหวะแบบหนึ่ง ...
Uriel

-1

นี่เป็นคำถามที่ดีมาก

วิธีการกำหนดเอกลักษณ์ของกระบวนการคือการดู (a) ที่อยู่ในหน่วยความจำ และ (b) หน่วยความจำนั้นมีอะไรบ้าง โดยเฉพาะเราต้องการทราบว่าในหน่วยความจำคือข้อความของโปรแกรมสำหรับการเริ่มต้นเนื่องจากเรารู้ว่าพื้นที่ข้อความของแต่ละเธรดจะใช้ตำแหน่งที่แตกต่างกันในหน่วยความจำ หากกระบวนการตายและเปิดตัวด้วย pid เดียวกันข้อความโปรแกรมสำหรับกระบวนการใหม่จะไม่ครอบครองสถานที่เดียวกันในหน่วยความจำและจะไม่มีข้อมูลเดียวกัน

ดังนั้นทันทีที่เริ่มกระบวนการของคุณให้ทำmd5sum /proc/[pid]/mapsและบันทึกผลลัพธ์ ต่อมาเมื่อคุณต้องการที่จะฆ่ากระบวนการทำ md5sum อื่นและเปรียบเทียบ ถ้ามันตรงกันแล้วฆ่า pid ถ้าไม่ทำไม่ได้

เพื่อดูสิ่งนี้ด้วยตัวคุณเองให้เปิดเปลือก bash สองอัน ตรวจสอบ/proc/[pid]/mapsสำหรับพวกเขาและคุณจะพบว่าพวกเขาแตกต่างกัน ทำไม? เพราะถึงแม้ว่ามันจะเป็นโปรแกรมเดียวกันพวกมันก็ใช้ตำแหน่งต่างกันในหน่วยความจำและที่อยู่ของสแต็กของมันก็ต่างกัน ดังนั้นหากกระบวนการของคุณตายและ PID ของมันถูกนำกลับมาใช้ใหม่แม้คำสั่งเดียวกันจะถูกเรียกใช้ใหม่ด้วยอาร์กิวเมนต์เดียวกันไฟล์ "แผนที่" จะแตกต่างกันและคุณจะรู้ว่าคุณไม่ได้จัดการกับกระบวนการดั้งเดิม

ดู: proc man pageสำหรับรายละเอียด

โปรดทราบว่าไฟล์/proc/[pid]/statนี้มีข้อมูลทั้งหมดที่ผู้โพสต์คนอื่น ๆ ได้กล่าวไว้ในคำตอบของพวกเขา: อายุของกระบวนการ, ผู้ปกครอง pid, ฯลฯ ไฟล์นี้มีทั้งข้อมูลคงที่และข้อมูลแบบไดนามิกดังนั้นหากคุณต้องการใช้ไฟล์นี้เป็นฐาน ของการเปรียบเทียบจากนั้นเมื่อคุณเปิดตัวคุณlongrunningthingจะต้องแตกฟิลด์แบบคงที่ต่อไปนี้ออกจากstatไฟล์และบันทึกไว้เพื่อทำการเปรียบเทียบในภายหลัง:

pid, ชื่อไฟล์, pid ของพาเรนต์, id กลุ่มกระบวนการ, เทอร์มินัลการควบคุม, เวลาเริ่มต้นหลังจากการบูตระบบ, ขนาดชุดที่อยู่อาศัย, ที่อยู่ของการเริ่มต้นของสแต็ก

นำมารวมกันข้างต้นระบุกระบวนการที่ไม่ซ้ำกันและเพื่อให้สิ่งนี้แสดงให้เห็นอีกวิธีที่จะไป จริงๆแล้วคุณสามารถหนีไปได้โดยไม่มีอะไรมากไปกว่า "pid" และ "กระบวนการเวลาเริ่มต้นหลังจากการบูตระบบ" ด้วยความมั่นใจระดับสูง เพียงแยกฟิลด์เหล่านี้ออกจากstatไฟล์และบันทึกไว้ที่ใดที่หนึ่งเมื่อเริ่มกระบวนการ ต่อมาก่อนที่จะฆ่ามันให้แตกอีกครั้งและเปรียบเทียบ หากพวกเขาตรงกันแล้วคุณมั่นใจได้ว่าคุณกำลังดูกระบวนการเดิม


1
ซึ่งโดยทั่วไปจะไม่ทำงานเมื่อมี/proc/[pid]/mapsการเปลี่ยนแปลงเมื่อเวลาผ่านไปเนื่องจากมีการจัดสรรหน่วยความจำเพิ่มเติมหรือสแต็คโตขึ้นหรือไฟล์ใหม่ถูก mmapped ... และหลังจากเปิดตัวหมายความว่าอย่างไร หลังจากไลบรารีทั้งหมดได้รับการแมปแล้ว? คุณจะกำหนดได้อย่างไร
Stéphane Chazelas

ฉันทำการทดสอบในระบบของฉันตอนนี้ด้วยสองกระบวนการหนึ่งแอปพลิเคชัน Java และอีกหนึ่งเซิร์ฟเวอร์ cfengine ฉันทำทุก 15 นาทีmd5sumกับไฟล์แผนที่ของพวกเขา ฉันจะให้มันทำงานสักวันหรือสองวันแล้วรายงานกลับมาที่นี่พร้อมผลลัพธ์
Michael Martinez

@ StéphaneChazelas: ฉันได้ตรวจสอบสองกระบวนการของฉันเป็นเวลา 16 ชั่วโมงแล้วและไม่มีการเปลี่ยนแปลงใน md5sum
Michael Martinez

-1

อีกวิธีหนึ่งคือการตรวจสอบอายุของกระบวนการก่อนที่จะฆ่ามัน ด้วยวิธีนี้คุณสามารถมั่นใจได้ว่าคุณไม่ได้ฆ่ากระบวนการที่ไม่ได้เกิดในเวลาน้อยกว่า 24 ชั่วโมง คุณสามารถเพิ่มifเงื่อนไขตามนั้นก่อนที่จะฆ่ากระบวนการ

if [[ $(ps -p $p -o etime=) =~ 1-. ]] ; then
    kill $p
fi

ifเงื่อนไขนี้จะตรวจสอบว่า ID กระบวนการ$pน้อยกว่า 24 ชั่วโมง (86400 วินาที)

PS: - คำสั่งps -p $p -o etime=จะมีรูปแบบ<no.of days>-HH:MM:SS


mtimeของ/proc/$pมีอะไรจะทำอย่างไรกับเวลาเริ่มต้นของกระบวนการ
Stéphane Chazelas

ขอบคุณ @ StéphaneChazelas คุณพูดถูก ฉันได้แก้ไขคำตอบเพื่อเปลี่ยนifเงื่อนไข โปรดแสดงความคิดเห็นหากรถของมัน
Sree

-3

สิ่งที่ฉันทำคือหลังจากที่ฆ่ากระบวนการไปแล้วให้ทำอีกครั้ง ทุกครั้งที่ฉันทำเช่นนั้นคำตอบจะกลับมา "ไม่มีกระบวนการดังกล่าว"

allenb   12084  5473  0 08:12 pts/4    00:00:00 man man
allenb@allenb-P7812 ~ $ kill -9 12084
allenb@allenb-P7812 ~ $ kill -9 12084
bash: kill: (12084) - No such process
allenb@allenb-P7812 ~ $ 

ไม่ง่ายกว่านี้และฉันทำมาหลายปีแล้วโดยไม่มีปัญหา


นั่นเป็นการตอบคำถาม "ฉันจะทำให้แย่ลงได้อย่างไร" ไม่ใช่ "ฉันจะแก้ไขได้อย่างไร"
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.