Làm cách nào tôi có thể giết quá trình và chắc chắn rằng PID chưa được sử dụng lại


40

Giả sử, ví dụ, bạn có một tập lệnh shell tương tự như:

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

Có nên làm mánh khóe không? Ngoại trừ quá trình có thể đã kết thúc sớm và PID của nó có thể đã được tái chế, có nghĩa là một số công việc vô tội nhận được một quả bom trong hàng đợi tín hiệu của nó. Trong thực tế điều này có thể có vấn đề, nhưng dù sao nó cũng làm tôi lo lắng. Việc hack lâu dài để tự chết, hoặc giữ / loại bỏ PID của nó trên FS sẽ làm được nhưng tôi đang nghĩ đến tình huống chung ở đây.


3
Bạn chắc chắn rằng nếu quá trình nhắm mục tiêu của bạn chết, nó sẽ giết chết kẻ giết người trong quá trình đó.
mikeerv

2
Sử dụng killallkhớp với tên, vì vậy ít nhất bạn chỉ giết một tiến trình có cùng tên với longrunningthing. Giả sử bạn sẽ chỉ có một trong số này chạy cùng một lúc.
LawrenceC

8
Bạn có thể lưu thời gian bắt đầu của quy trình ban đầu và trước khi giết, hãy kiểm tra xem thời gian bắt đầu của quy trình đó có phù hợp với những gì bạn đã lưu không. Cặp pid, thời gian bắt đầu một định danh duy nhất cho các quy trình trong Linux.
Bakuriu

1
Tôi có thể hỏi tại sao bạn cần điều này? điều cơ bản bạn đang cố gắng để đạt được là gì? (một cái gì đó chạy liên tục nhưng được đặt lại sau mỗi 24 giờ?)
Olivier Dulac

2
@mikeerv Một quá trình không thể đảm bảo rằng điều gì đó sẽ xảy ra trong trường hợp cái chết của chính nó.
kasperd

Câu trả lời:


29

Tốt nhất là sử dụng timeoutlệnh nếu bạn có nó có nghĩa là:

timeout 86400 cmd

Việc triển khai GNU (8.23) hiện tại ít nhất hoạt động bằng cách sử dụng alarm()hoặc tương đương trong khi chờ tiến trình con. Nó dường như không bảo vệ chống lại việc SIGALRMđược giao ở giữa waitpid()trở về và timeoutthoát (có hiệu quả hủy bỏ báo động đó ). Trong cửa sổ nhỏ đó, timeoutthậm chí có thể viết tin nhắn trên stderr (ví dụ nếu đứa trẻ đổ lõi) sẽ mở rộng thêm cửa sổ cuộc đua đó (vô thời hạn nếu stderr là một ống đầy đủ).

Cá nhân tôi có thể sống với giới hạn đó (có thể sẽ được sửa trong phiên bản tương lai). timeoutcũng sẽ cẩn thận hơn để báo cáo trạng thái thoát chính xác, xử lý các trường hợp góc khác (như SIGALRM bị chặn / bỏ qua khi khởi động, xử lý các tín hiệu khác ...) tốt hơn bạn có thể xử lý bằng tay.

Là một xấp xỉ, bạn có thể viết nó perlnhư sau:

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

Có một timelimitlệnh tại http://devel.ringlet.net/sysutils/tim006it/ (trước GNU timeoutvài tháng).

 timelimit -t 86400 cmd

Cái đó sử dụng một alarm()cơ chế giống như nhưng cài đặt một trình xử lý SIGCHLD(bỏ qua những đứa trẻ đã dừng lại) để phát hiện đứa trẻ sắp chết. Nó cũng hủy báo thức trước khi chạy waitpid()(không hủy giao hàng SIGALRMnếu nó đang chờ xử lý, nhưng theo cách viết, tôi không thể thấy đó là sự cố) và giết chết trước khi gọi waitpid()(vì vậy không thể giết chết một pid được sử dụng lại ).

netpipes cũng có một timelimitlệnh. Cái đó có trước tất cả những cái khác trong nhiều thập kỷ, có một cách tiếp cận khác, nhưng không hoạt động đúng với các lệnh đã dừng và trả về 1trạng thái thoát khi hết thời gian.

Là một câu trả lời trực tiếp hơn cho câu hỏi của bạn, bạn có thể làm một cái gì đó như:

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

Đó là, kiểm tra xem quá trình này vẫn là con của chúng ta. Một lần nữa, có một cửa sổ chủng tộc nhỏ (ở giữa pslấy trạng thái của quá trình đó và killgiết nó) trong quá trình đó có thể chết và pid của nó được sử dụng lại bởi một quy trình khác.

Với một số vỏ ( zsh, bash, mksh), bạn có thể vượt qua thông số kỹ thuật công việc thay vì PID.

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

Điều đó chỉ hoạt động nếu bạn sinh ra chỉ một công việc nền (nếu không thì việc làm đúng công việc không phải lúc nào cũng đáng tin cậy).

Nếu đó là một vấn đề, chỉ cần bắt đầu một phiên bản shell mới:

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

Điều đó hoạt động vì vỏ loại bỏ công việc khỏi bảng công việc khi đứa trẻ chết. Ở đây, không nên có bất kỳ cửa sổ cuộc đua nào kể từ khi shell gọi kill(), tín hiệu SIGCHLD chưa được xử lý và pid không thể được sử dụng lại (vì nó chưa được chờ) hoặc đã được xử lý và công việc đã bị xóa khỏi bảng quy trình (và killsẽ báo lỗi). bashkillít nhất khối SIGCHLD trước khi nó truy cập vào bảng công việc của mình để mở rộng %và unblocks nó sau kill().

Một lựa chọn khác để tránh sleepquá trình đó bị treo ngay cả sau khi cmdđã chết, có bashhoặc ksh93sử dụng một đường ống read -tthay vì sleep:

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

Cái đó vẫn có điều kiện chủng tộc và bạn mất trạng thái thoát lệnh. Nó cũng giả sử cmdkhông đóng fd 4 của nó.

Bạn có thể thử thực hiện một giải pháp không có chủng tộc perlnhư:

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...

(mặc dù nó sẽ cần phải được cải thiện để xử lý các loại trường hợp góc khác).

Một phương pháp không có chủng tộc khác có thể sử dụng các nhóm quy trình:

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

Tuy nhiên, lưu ý rằng việc sử dụng các nhóm quy trình có thể có tác dụng phụ nếu có I / O cho thiết bị đầu cuối có liên quan. Nó có lợi ích bổ sung mặc dù để tiêu diệt tất cả các quá trình bổ sung khác được sinh ra bởi cmd.


4
Tại sao không đề cập đến phương pháp tốt nhất đầu tiên?
deltab

2
@deltab: timeoutkhông phải là hàng xách tay, câu trả lời đã đề cập đến một giải pháp di động trước tiên.
cuonglm

1
@deltab: nó cung cấp cái nhìn sâu sắc về cách mọi thứ hoạt động và đặc biệt là cách tiếp cận "lẽ thường" có thể thất bại (Stephane thích dạy một người câu cá trước, điều mà tôi thích). Một người dự kiến ​​sẽ đọc toàn bộ câu trả lời
Olivier Dulac

@Stephane: đối với "việc tìm đúng công việc không phải lúc nào cũng đáng tin cậy": trước tiên bạn không thể đếm đầu ra jobsvà sau đó biết rằng (vì đó là vỏ của chính bạn, trong đó bạn có quyền kiểm soát những gì xảy ra tiếp theo) công việc sẽ là N + 1? [sau đó bạn có thể lưu N và sau đó giết% N + 1])
Olivier Dulac

1
@OlivierDulac, điều đó sẽ cho rằng không có công việc nào trong quá khứ bị chấm dứt khi bạn bắt đầu một công việc mới (shells sử dụng lại số công việc).
Stéphane Chazelas

28

Nói chung, bạn không thể. Tất cả các câu trả lời được đưa ra cho đến nay là lỗi heuristic. Chỉ có một trường hợp trong đó bạn có thể sử dụng pid một cách an toàn để gửi tín hiệu: khi quy trình đích là con trực tiếp của quy trình sẽ gửi tín hiệu và cha mẹ chưa đợi nó. Trong trường hợp này, ngay cả khi nó đã thoát, pid vẫn được bảo lưu (đây là "quá trình zombie") cho đến khi cha mẹ chờ đợi nó. Tôi không biết cách nào để làm điều đó một cách sạch sẽ với cái vỏ.

Một cách an toàn khác để tiêu diệt các tiến trình là bắt đầu chúng với một tty điều khiển được đặt thành một thiết bị đầu cuối giả mà bạn sở hữu phía chủ. Sau đó, bạn có thể gửi tín hiệu qua thiết bị đầu cuối, ví dụ viết ký tự cho SIGTERMhoặc SIGQUITqua pty.

Tuy nhiên, một cách khác thuận tiện hơn với kịch bản là sử dụng một screenphiên có tên và gửi lệnh đến phiên màn hình để kết thúc nó. Quá trình này diễn ra trên một ổ cắm ống hoặc unix được đặt tên theo phiên màn hình, sẽ không tự động được sử dụng lại nếu bạn chọn một tên duy nhất an toàn.


4
Tôi không thể hiểu tại sao điều đó không thể được thực hiện trong vỏ. Tôi đã đưa ra một số giải pháp.
Stéphane Chazelas

3
Bạn có thể vui lòng đưa ra một lời giải thích và một số loại thảo luận định lượng về cửa sổ cuộc đua và những hạn chế khác? Không có điều đó, "Tất cả các câu trả lời được đưa ra cho đến nay đều là lỗi heuristic" chỉ là một cuộc đối đầu không cần thiết mà không có bất kỳ lợi ích nào.
peterph

3
@peterph: Nói chung, bất kỳ việc sử dụng pid nào cũng là chủng tộc TOCTOU - cho dù bạn kiểm tra xem nó có còn đề cập đến cùng một quy trình mà bạn mong đợi hay không, nó có thể ngừng tham khảo quy trình đó và tham khảo một số quy trình mới xử lý trong khoảng thời gian trước khi bạn sử dụng nó (gửi tín hiệu). Cách duy nhất để ngăn chặn điều này là có thể chặn việc giải phóng / tái sử dụng pid và quá trình duy nhất có thể làm điều này là cha mẹ trực tiếp.
R ..

2
@ StéphaneChazelas: Làm thế nào để bạn ngăn chặn vỏ chờ đợi trong một quá trình nền đã thoát? Nếu bạn có thể làm điều đó, vấn đề có thể dễ dàng giải quyết trong trường hợp OP cần.
R ..

5
@peterph: "Cửa sổ cuộc đua nhỏ" không phải là một giải pháp. Và sự hiếm hoi của cuộc đua phụ thuộc vào sự phân công liên tiếp. Lỗi gây ra một cái gì đó rất xấu xảy ra một lần trong năm còn tồi tệ hơn nhiều so với các lỗi xảy ra mọi lúc vì chúng hầu như không thể chẩn đoán và sửa chữa.
R ..

10
  1. Khi khởi chạy quá trình, hãy tiết kiệm thời gian bắt đầu:

    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. Trước khi cố gắng giết quá trình, hãy dừng quá trình này (điều này không thực sự cần thiết, nhưng đó là cách để tránh các điều kiện chủng tộc: nếu bạn dừng quá trình, pid không thể được sử dụng lại)

    kill -s STOP "$p"
    
  3. Kiểm tra xem quy trình với PID đó có cùng thời gian bắt đầu không và nếu có, hãy giết nó, nếu không hãy để quá trình tiếp tục:

    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
    

Điều này hoạt động vì chỉ có thể có một quá trình với cùng một PID thời gian bắt đầu trên một HĐH nhất định.

Dừng quá trình trong quá trình kiểm tra làm cho điều kiện cuộc đua không thành vấn đề. Rõ ràng điều này có vấn đề rằng, một số quá trình ngẫu nhiên có thể bị dừng lại trong một vài phần nghìn giây. Tùy thuộc vào loại quy trình, điều này có thể hoặc không phải là một vấn đề.


Cá nhân tôi chỉ đơn giản là sử dụng python và psutiltự động xử lý tái sử dụng 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()

Các quy tắc Python trong UNIX ... Tôi không chắc tại sao nhiều câu trả lời không bắt đầu ở đó vì tôi chắc chắn rằng hầu hết các hệ thống không cấm sử dụng nó.
Ông Mascaro

Tôi đã sử dụng một sơ đồ tương tự (sử dụng thời gian bắt đầu) trước đây, nhưng kỹ năng viết kịch bản sh của bạn gọn gàng hơn của tôi! Cảm ơn.
FJL

Điều đó có nghĩa là bạn có khả năng dừng quá trình sai. Lưu ý rằng ps -o start=định dạng thay đổi từ 18h12 đến 26/1 sau một thời gian. Coi chừng thay đổi DST là tốt. Nếu trên Linux, có lẽ bạn sẽ thích TZ=UTC0 ps -o lstart=.
Stéphane Chazelas

@ StéphaneChazelas Có, nhưng bạn để nó tiếp tục sau đó. Tôi nói rõ ràng: tùy thuộc vào loại nhiệm vụ mà quá trình đó đang thực hiện, bạn có thể gặp một số rắc rối khi dừng nó vài mili giây. Cảm ơn về mẹo trên lstart, tôi sẽ chỉnh sửa nó.
Bakuriu

Lưu ý rằng (trừ khi hệ thống của bạn giới hạn số lượng quy trình trên mỗi người dùng), mọi người đều dễ dàng lấp đầy bảng quy trình với zombie. Khi chỉ còn 3 pids có sẵn, mọi người có thể dễ dàng bắt đầu hàng trăm quy trình khác nhau với cùng một pid trong một giây. Vì vậy, nói một cách nghiêm túc, "chỉ có thể có một quá trình với cùng một PID và thời gian bắt đầu trên một HĐH nhất định" không nhất thiết đúng.
Stéphane Chazelas

7

Trên hệ thống linux, bạn có thể đảm bảo rằng pid sẽ không được sử dụng lại bằng cách giữ cho không gian tên pid của nó tồn tại. Điều này có thể được thực hiện thông qua các /proc/$pid/ns/pidtập tin.

  • man namespaces -

    Gắn kết (xem mount(2)) một trong các tệp trong thư mục này đến một nơi khác trong hệ thống tệp giữ cho không gian tên tương ứng của quy trình được chỉ định bởi pid còn sống ngay cả khi tất cả các quy trình hiện tại trong không gian tên kết thúc.

    Mở một trong các tệp trong thư mục này (hoặc một tệp được gắn kết với một trong các tệp này) sẽ trả về một tệp xử lý cho không gian tên tương ứng của quy trình được chỉ định bởi pid. Miễn là bộ mô tả tệp này vẫn mở, không gian tên sẽ vẫn tồn tại, ngay cả khi tất cả các quy trình trong không gian tên chấm dứt. Bộ mô tả tập tin có thể được chuyển đến setns(2).

Bạn có thể cô lập một nhóm các quy trình - về cơ bản là bất kỳ số lượng quy trình nào - bằng cách đặt tên cho chúng init.

  • man pid_namespaces -

    Quá trình đầu tiên được tạo ra trong một không gian tên mới (ví dụ, quá trình này tạo ra sử dụng clone(2) với CLONE_NEWPID cờ, hoặc đứa trẻ đầu tiên được tạo ra bởi một quá trình sau khi một cuộc gọi đến unshare(2)bằng cách sử dụng CLONE_NEWPID cờ) có các PID 1 , và là initquá trình cho không gian tên ( xem init(1)) . Một tiến trình con được mồ côi trong không gian tên sẽ được sửa lại cho quá trình này chứ không phải init(1) (trừ khi một trong những tổ tiên của đứa trẻ trong cùng một không gian tên PID sử dụng lệnh prctl(2) PR_SET_CHILD_SUBREAPER để đánh dấu chính nó là quá trình hạ lưu của trẻ mồ côi) .

    Nếu initquá trình của một không gian tên PID chấm dứt, kernel sẽ chấm dứt tất cả các quy trình trong không gian tên thông qua tín hiệu SIGKILL . Hành vi này phản ánh thực tế rằng initquy trình này rất cần thiết cho hoạt động chính xác của không gian tên PID .

Các util-linuxgói cung cấp nhiều công cụ hữu ích để thao tác với không gian tên. Ví dụ: unsharemặc dù, nếu bạn chưa sắp xếp các quyền của nó trong không gian tên người dùng, nó sẽ yêu cầu quyền siêu người dùng:

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

Nếu bạn chưa sắp xếp cho một không gian tên người dùng, thì bạn vẫn có thể thực thi các lệnh tùy ý một cách an toàn bằng cách bỏ ngay các đặc quyền. Các runuserlệnh khác (không setuid) nhị phân được cung cấp bởi các util-linuxgói và kết hợp nó có thể trông giống như:

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

... và cứ thế.

Trong ví dụ trên hai công tắc được truyền cho unshare(1)những --forklá cờ mà làm cho Viện dẫn sh -cquá trình đứa trẻ đầu tiên được tạo ra và đảm bảo nó inittrạng thái và những --pidlá cờ mà chỉ thị unshare(1)để tạo ra một không gian tên pid.

Các sh -cquá trình bầy lăm vỏ con backgrounded - mỗi một inifinite whilevòng lặp sẽ tiếp tục gắn đầu ra của dateđến cuối logcàng lâu càng sleep 1trở thành sự thật. Sau khi sinh ra các quá trình này shgọi sleepthêm 5 giây rồi chấm dứt.

Có thể đáng chú ý rằng nếu -fcờ không được sử dụng thì không có whilevòng lặp nền nào sẽ chấm dứt, nhưng với nó ...

ĐẦU RA:

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

Câu trả lời thú vị mà dường như là mạnh mẽ. Có lẽ hơi quá mức cho việc sử dụng cơ bản, nhưng đáng để suy nghĩ.
Uriel

Tôi không thấy làm thế nào hoặc tại sao việc giữ cho một không gian tên PID tồn tại ngăn chặn việc tái sử dụng một PID. Chính trang bạn trích dẫn - Miễn là bộ mô tả tệp này vẫn mở, không gian tên sẽ vẫn tồn tại, ngay cả khi tất cả các quy trình trong không gian tên chấm dứt - cho thấy các quy trình vẫn có thể chấm dứt (và do đó có thể được tái chế ID tiến trình của chúng). Việc giữ cho không gian tên PID còn tồn tại có liên quan gì đến việc ngăn chặn bản thân PID không được sử dụng lại bởi một quy trình khác?
davmac

5

Cân nhắc làm cho longrunningthinghành vi của bạn tốt hơn một chút, giống daemon hơn một chút. Ví dụ, bạn có thể làm cho nó tạo ra một pidfile cho phép ít nhất một số kiểm soát hạn chế của quá trình. Có một số cách để làm điều này mà không sửa đổi nhị phân ban đầu, tất cả đều liên quan đến một trình bao bọc. Ví dụ:

  1. một tập lệnh trình bao bọc đơn giản sẽ bắt đầu công việc được yêu cầu ở chế độ nền (với chuyển hướng đầu ra tùy chọn), viết PID của quy trình này vào một tệp, sau đó đợi quá trình kết thúc (sử dụng wait) và xóa tệp. Nếu trong thời gian chờ đợi, quá trình bị giết, ví dụ như

    kill $(cat pidfile)
    

    trình bao bọc sẽ chỉ đảm bảo pidfile được gỡ bỏ.

  2. một trình bao bọc màn hình, sẽ đặt PID riêng của nó ở đâu đó và bắt (và trả lời) các tín hiệu được gửi đến nó. Ví dụ đơn giản:

    #!/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"

Bây giờ, như @R .. và @ StéphaneChazelas đã chỉ ra, những cách tiếp cận này thường có một điều kiện chủng tộc ở đâu đó hoặc áp đặt một hạn chế về số lượng quy trình bạn có thể sinh ra. Ngoài ra, nó không xử lý các trường hợp, trong đó longrunningthingngã ba có thể và trẻ em bị tách ra (có lẽ không phải là vấn đề trong câu hỏi ban đầu).

Với các nhân Linux gần đây (đã đọc vài năm tuổi), điều này có thể được xử lý độc đáo bằng cách sử dụng các nhóm , cụ thể là tủ đông - mà theo tôi, là một số hệ thống khởi tạo Linux hiện đại sử dụng.


Cảm ơn, và cho tất cả. Tôi đang đọc mọi thứ ngay bây giờ. Quan điểm longrunningthinglà bạn không kiểm soát được nó là gì. Tôi cũng đã đưa ra một ví dụ kịch bản shell bởi vì nó giải thích vấn đề. Tôi thích của bạn và tất cả các giải pháp sáng tạo khác ở đây, nhưng nếu bạn đang sử dụng Linux / bash thì sẽ có phần "hết thời gian" cho việc đó. Tôi cho rằng tôi nên lấy nguồn đó và xem nó hoạt động như thế nào!
FJL

@FJL, timeoutkhông một BUILTIN vỏ. Đã có nhiều cách triển khai một timeoutlệnh cho Linux, một lệnh gần đây (2008) đã được thêm vào GNU coreutils (không phải là Linux cụ thể) và đó là điều mà hầu hết các bản phân phối Linux hiện nay sử dụng.
Stéphane Chazelas

@ Stéphane - Cảm ơn - Sau đó tôi đã tìm thấy tài liệu tham khảo về lõi core GNU. Chúng có thể là hàng xách tay, nhưng trừ khi nó nằm trong hệ thống cơ sở thì không thể dựa vào nó. Tôi quan tâm nhiều hơn đến việc biết nó hoạt động như thế nào, mặc dù tôi lưu ý nhận xét của bạn ở nơi khác cho thấy nó không đáng tin cậy 100%. Theo cách mà chủ đề này đã đi, tôi không ngạc nhiên!
FJL

1

Nếu bạn đang chạy trên Linux (và một vài * nixes khác), bạn có thể kiểm tra xem quy trình bạn định giết có còn được sử dụng không dòng lệnh có khớp với quy trình dài của bạn không. Cái gì đó như :

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

Một cách khác có thể là kiểm tra xem quá trình bạn định giết bao lâu sẽ diễn ra, với một cái gì đó như thế nào ps -p $p -o etime=. Bạn có thể tự làm điều đó bằng cách trích xuất thông tin này từ đó /proc/$p/stat, nhưng điều này sẽ rất khó khăn (thời gian được tính bằng jiffies và bạn cũng sẽ phải sử dụng thời gian hoạt động của hệ thống /proc/stat).

Dù sao, bạn thường không thể đảm bảo rằng quy trình không bị thay thế sau khi kiểm tra và trước khi bạn giết nó.


Điều đó vẫn không đúng bởi vì nó không thoát khỏi điều kiện cuộc đua.
strcat

@strcat Thật vậy, không bảo đảm thành công, nhưng hầu hết các tập lệnh thậm chí không bận tâm đến việc kiểm tra như vậy và chỉ giết chết một cat pidfilekết quả. Tôi không thể nhớ một cách sạch sẽ để làm điều đó chỉ trong vỏ. Tuy nhiên, câu trả lời không gian tên được đề xuất có vẻ như là một câu hỏi xen kẽ ...
Uriel

-1

Đây thực sự là một câu hỏi rất hay.

Cách để xác định tính duy nhất của quá trình là xem xét (a) vị trí của nó trong bộ nhớ; và (b) những gì bộ nhớ đó chứa. Để cụ thể, chúng tôi muốn biết nơi trong bộ nhớ là văn bản chương trình cho lệnh gọi ban đầu, bởi vì chúng tôi biết rằng vùng văn bản của mỗi luồng sẽ chiếm một vị trí khác nhau trong bộ nhớ. Nếu quá trình chết và một cái khác được khởi chạy với cùng một pid, văn bản chương trình cho quy trình mới sẽ không chiếm cùng một vị trí trong bộ nhớ và sẽ không chứa cùng thông tin.

Vì vậy, ngay sau khi khởi chạy quy trình của bạn, hãy làm md5sum /proc/[pid]/mapsvà lưu kết quả. Sau này khi bạn muốn giết tiến trình, hãy thực hiện một md5sum khác và so sánh nó. Nếu nó phù hợp, sau đó giết pid. Nếu không, đừng.

để thấy điều này cho chính mình, khởi chạy hai vỏ bash giống hệt nhau. Kiểm tra /proc/[pid]/mapscho họ và bạn sẽ thấy rằng họ là khác nhau. Tại sao? Bởi vì mặc dù đó là cùng một chương trình, chúng chiếm các vị trí khác nhau trong bộ nhớ và địa chỉ của ngăn xếp của chúng là khác nhau. Vì vậy, nếu quy trình của bạn chết và PID của nó được sử dụng lại, ngay cả khi cùng một lệnh được khởi chạy lại với cùng một đối số , tệp "maps" sẽ khác và bạn sẽ biết rằng bạn không xử lý quy trình ban đầu.

Xem: trang người đàn ông để biết chi tiết.

Lưu ý rằng tệp /proc/[pid]/statđã chứa tất cả thông tin mà người đăng khác đã đề cập trong câu trả lời của họ: tuổi của quy trình, tệp gốc, v.v. Tệp này chứa cả thông tin tĩnh và thông tin động, vì vậy nếu bạn muốn sử dụng tệp này làm cơ sở so sánh, sau đó khi khởi chạy longrunningthing, bạn cần trích xuất các trường tĩnh sau từ stattệp và lưu chúng để so sánh sau:

pid, tên tệp, pid của cha mẹ, id nhóm quy trình, thiết bị đầu cuối kiểm soát, quá trình thời gian bắt đầu sau khi khởi động hệ thống, kích thước cài đặt thường trú, địa chỉ bắt đầu của ngăn xếp,

được thực hiện cùng nhau, ở trên xác định duy nhất quá trình, và do đó, điều này đại diện cho một cách khác để đi. Trên thực tế, bạn có thể thoát khỏi không có gì ngoài "pid" và "quá trình thời gian bắt đầu sau khi khởi động hệ thống" với độ tin cậy cao. Chỉ cần trích xuất các trường này từ stattệp và lưu nó ở đâu đó khi khởi chạy quy trình của bạn. Sau đó trước khi giết nó, giải nén nó một lần nữa và so sánh. Nếu chúng khớp, thì bạn yên tâm rằng bạn đang xem quy trình ban đầu.


1
Điều đó thường sẽ không hoạt động khi /proc/[pid]/mapsthay đổi theo thời gian khi bộ nhớ bổ sung được phân bổ hoặc ngăn xếp phát triển hoặc các tệp mới bị chặn ... Và ngay sau khi khởi chạy có nghĩa là gì? Sau khi tất cả các thư viện đã được mmaps? Làm thế nào để bạn xác định rằng?
Stéphane Chazelas

Bây giờ tôi đang thực hiện một thử nghiệm trên hệ thống của mình với hai quy trình, một ứng dụng java và một máy chủ cengine. Cứ sau 15 phút tôi làm md5sumtrên các tập tin bản đồ của họ. Tôi sẽ để nó chạy trong một hoặc hai ngày và báo cáo lại kết quả tại đây.
Michael Martinez

@ StéphaneChazelas: Tôi đã kiểm tra hai quy trình của mình trong 16 giờ và không có thay đổi nào trong md5sum
Michael Martinez

-1

Một cách khác là kiểm tra tuổi của quá trình trước khi giết nó. Bằng cách đó, bạn có thể chắc chắn rằng mình không giết chết một quy trình không sinh ra trong vòng chưa đầy 24 giờ. Bạn có thể thêm một ifđiều kiện dựa trên điều đó trước khi giết quá trình.

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

Điều ifkiện này sẽ kiểm tra xem ID quá trình $pcó ít hơn 24 giờ không (86400 giây).

PS: - Lệnh ps -p $p -o etime=sẽ có định dạng<no.of days>-HH:MM:SS


Các mtimesố /proc/$pkhông có gì để làm với thời gian bắt đầu của quá trình.
Stéphane Chazelas

Cảm ơn @ StéphaneChazelas. Bạn đúng rồi. Tôi đã chỉnh sửa câu trả lời để thay đổi ifđiều kiện. Xin vui lòng bình luận nếu lỗi của nó.
Sree

-3

Những gì tôi làm là, sau khi đã giết quá trình, làm lại. Mỗi khi tôi làm điều đó, câu trả lời lại xuất hiện, "không có quá trình như vậy"

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 ~ $ 

Không thể đơn giản hơn và tôi đã làm điều này trong nhiều năm mà không có vấn đề gì.


Đó là trả lời câu hỏi "làm thế nào tôi có thể làm cho nó tồi tệ hơn", không phải là "làm thế nào tôi có thể sửa chữa nó".
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.