Bash: tidur tanpa batas (pemblokiran tak terbatas)

158

Saya gunakan startxuntuk memulai X yang akan mengevaluasi .xinitrc. Di saya, .xinitrcsaya mulai menggunakan window manager saya /usr/bin/mywm. Sekarang, jika saya membunuh WM saya (untuk menguji beberapa WM lainnya), X akan berakhir juga karena .xinitrcscript mencapai EOF. Jadi saya menambahkan ini di akhir .xinitrc:

while true; do sleep 10000; done

Dengan cara ini X tidak akan berhenti jika saya membunuh WM saya. Sekarang pertanyaan saya: bagaimana saya bisa tidur nyenyak alih-alih tidur berulang? Apakah ada perintah yang agak suka membekukan skrip?

salam Hormat

Watain
sumber

Jawaban:

330

sleep infinity melakukan apa yang disarankan dan berfungsi tanpa penyalahgunaan kucing.

Donarsson
sumber
16
Keren. Sayangnya, busybox saya tidak mengerti.
not-a-user
12
BSD (atau setidaknya OS X) juga tidak mengerti sleep infinity, meskipun itu hal yang keren untuk dipelajari tentang Linux. Namun, while true; do sleep 86400; doneharus menjadi pengganti yang memadai.
Ivan X
16
Mengenai hal ini, saya membuat beberapa penelitian yang saya dokumentasikan dalam jawaban terpisah. Untuk meringkas: infinitydikonversi dalam C dari "string" ke a double. Kemudian itu doubledipotong ke nilai maksimum yang diperbolehkan timespec, yang berarti jumlah detik yang sangat besar (tergantung arsitektur) tetapi, secara teori, terbatas.
jp48
72

tail tidak menghalangi

Seperti biasa: Untuk semuanya ada jawaban yang pendek, mudah dimengerti, mudah diikuti dan sepenuhnya salah. Di sini tail -f /dev/nulltermasuk dalam kategori ini;)

Jika Anda melihatnya dengan strace tail -f /dev/nullAnda akan melihat, bahwa solusi ini jauh dari pemblokiran! Ini mungkin bahkan lebih buruk daripada sleepsolusi dalam pertanyaan, karena menggunakan sumber daya berharga (di Linux) seperti inotifysistem. Juga proses lain yang menulis untuk /dev/nullmembuat taillingkaran. (Pada Ubuntu64 16.10 saya, ini menambahkan beberapa 10 syscalls per detik pada sistem yang sudah sibuk.)

Pertanyaannya adalah untuk perintah pemblokiran

Sayangnya, tidak ada yang namanya ..

Baca: Saya tidak tahu cara mengarsipkan ini dengan shell secara langsung.

Semuanya (datar sleep infinity) dapat terganggu oleh beberapa sinyal. Jadi jika Anda ingin benar-benar yakin itu tidak kembali, itu harus dijalankan dalam satu lingkaran, seperti yang sudah Anda lakukan untuk Anda sleep. Harap dicatat, bahwa (di Linux) /bin/sleeptampaknya dibatasi dalam 24 hari (lihatlah strace sleep infinity), maka yang terbaik yang dapat Anda lakukan adalah:

while :; do sleep 2073600; done

(Perhatikan bahwa saya percaya sleeploop internal untuk nilai yang lebih tinggi dari 24 hari, tetapi ini berarti: Ini tidak memblokir, looping sangat lambat. Jadi mengapa tidak memindahkan loop ini ke luar?)

.. tetapi Anda bisa datang cukup dekat dengan yang tidak disebutkan namanya fifo

Anda dapat membuat sesuatu yang benar-benar menghalangi selama tidak ada sinyal yang dikirim ke proses. Penggunaan berikut bash 4, 2 PID dan 1 fifo:

bash -c 'coproc { exec >&-; read; }; eval exec "${COPROC[0]}<&-"; wait'

Anda dapat memeriksa apakah ini benar-benar diblokir stracejika Anda suka:

strace -ff bash -c '..see above..'

Bagaimana ini dibangun

readblok jika tidak ada data input (lihat beberapa jawaban lain). Namun, tty(alias. stdin) Biasanya bukan sumber yang baik, karena ditutup ketika pengguna logout. Juga mungkin mencuri beberapa masukan dari tty. Tidak baik.

Untuk membuat readblok, kita perlu menunggu sesuatu seperti fifoyang tidak akan pernah mengembalikan apa pun. Dalam bash 4ada perintah yang tepat dapat memberikan kita dengan seperti fifo: coproc. Jika kita juga menunggu pemblokiran read(yang merupakan milik kita coproc), kita selesai. Sayangnya ini harus tetap membuka dua PID dan a fifo.

Varian dengan nama fifo

Jika Anda tidak ingin menggunakan nama fifo, Anda bisa melakukan ini sebagai berikut:

mkfifo "$HOME/.pause.fifo" 2>/dev/null; read <"$HOME/.pause.fifo"

Tidak menggunakan perulangan pada proses baca agak ceroboh, tetapi Anda dapat menggunakan kembali ini fifosesering mungkin dan membuat readterminat menggunakan touch "$HOME/.pause.fifo"(jika ada lebih dari satu menunggu baca, semua diakhiri sekaligus).

Atau gunakan pause()syscall Linux

Untuk pemblokiran tak terbatas ada panggilan kernel Linux, yang disebut pause(), yang melakukan apa yang kita inginkan: Tunggu selamanya (sampai sinyal tiba). Namun tidak ada program userspace untuk ini (belum).

C

Membuat program seperti itu mudah. Berikut ini cuplikan untuk membuat program Linux yang sangat kecil yang disebut pausejeda tanpa batas waktu (kebutuhan diet, gccdll.):

printf '#include <unistd.h>\nint main(){for(;;)pause();}' > pause.c;
diet -Os cc pause.c -o pause;
strip -s pause;
ls -al pause

python

Jika Anda tidak ingin mengkompilasi sesuatu sendiri, tetapi Anda telah pythonmenginstalnya, Anda dapat menggunakan ini di Linux:

python -c 'while 1: import ctypes; ctypes.CDLL(None).pause()'

(Catatan: Gunakan exec python -c ...untuk mengganti shell saat ini, ini membebaskan satu PID. Solusinya dapat ditingkatkan dengan beberapa pengalihan IO juga, membebaskan FD yang tidak terpakai. Ini terserah Anda.)

Bagaimana ini bekerja (saya pikir): ctypes.CDLL(None)memuat perpustakaan C standar dan menjalankan pause()fungsi di dalamnya dalam beberapa loop tambahan. Kurang efisien daripada versi C, tetapi berfungsi.

Rekomendasi saya untuk Anda:

Menginap di tidur berulang. Sangat mudah dipahami, sangat portabel, dan hampir selalu memblokir.

Tino
sumber
1
@ Andrew Biasanya Anda tidak memerlukan trap(yang mengubah perilaku shell ke sinyal) atau latar belakang (yang memungkinkan shell untuk mencegat sinyal dari terminal, seperti Strg + C). Jadi sleep infinitysudah cukup (berperilaku seperti exec sleep infinityjika itu adalah pernyataan terakhir. Untuk melihat perbedaan penggunaan strace -ffDI4 bash -c 'YOURCODEHERE'). Tidur berulang lebih baik, karena sleepdapat kembali dalam keadaan tertentu. Misalnya, Anda tidak ingin X11 dimatikan secara tiba-tiba pada killall sleep, hanya karena .xstartupberakhir sleep infinitysebagai ganti lingkaran tidur.
Tino
Mungkin sedikit tidak jelas, tetapi s6-pausemerupakan perintah userland untuk dijalankan pause(), opsional mengabaikan berbagai sinyal.
Patrick
@Tino /bin/sleeptidak dibatasi pada 24 hari seperti yang Anda katakan. Akan lebih baik jika Anda dapat memperbarui itu. Di Linux sekarang, kode ini aktif. Ini membatasi nanosleep()syscalls individu hingga 24 hari, tetapi menyebutnya dalam satu lingkaran. Jadi sleep infinitysebaiknya tidak keluar setelah 24 hari. The doubleinfinity positif akan dikonversi ke struct timespec. Melihat rpl_nanosleepdi GDB, infinityakan dikonversi ke { tv_sec = 9223372036854775807, tv_nsec = 999999999 }di Ubuntu 16,04.
nh2
@ nh2 Sudah disebutkan dalam teks bahwa tidur mungkin loop bukannya sepenuhnya diblokir. Saya mengeditnya sekarang sedikit untuk semoga membuat fakta ini sedikit lebih jelas. Harap perhatikan ini " mungkin ", karena dari stracesendirian saya tidak dapat membuktikan fakta bahwa ada beberapa kode pengulangan yang dikompilasi sleep, dan saya tidak ingin menunggu 24 hari hanya untuk menguji ini (atau mendekompilasi /bin/sleep). Itu selalu lebih baik untuk memprogram defensif, jika tidak ada bukti matematika yang keras, bahwa sesuatu benar-benar, seperti yang terlihat. Juga tidak pernah mempercayai apa pun:killall -9 sleep
Tino
Opsi pause () dapat dilakukan dengan cukup mudah dengan perl: perl -MPOSIX -e 'pause ()'
tgoodhart
70

Mungkin ini tampak jelek, tapi mengapa tidak jalankan saja catdan biarkan menunggu input selamanya?

Michał Trybus
sumber
4
Ini tidak berfungsi jika Anda tidak memiliki pipa gantung yang bisa dibaca. Mohon saran.
Matt Joiner
2
@ Matt, mungkin membuat pipa dan catitu? mkfifo pipe && cat pipe
Michał Trybus
Apa yang dikatakan @twalberg, tetapi juga Anda dapat segera menetapkan ulang ke 3 dan memutuskan tautannya, seperti yang ditunjukkan di sini: superuser.com/a/633185/762481
jp48
32

TL; DR: sleep infinitysebenarnya tidur waktu maksimum yang diizinkan, yang terbatas.

Bertanya-tanya mengapa ini tidak didokumentasikan di mana pun, saya repot-repot membaca sumber - sumber dari GNU coreutils dan saya menemukan itu mengeksekusi kira-kira apa yang berikut:

  1. Gunakan strtoddari C stdlib pada argumen pertama untuk mengubah 'infinity' menjadi presisi ganda. Jadi, dengan asumsi presisi ganda IEEE 754 nilai infinity positif 64-bit disimpan dalam secondsvariabel.
  2. Invoke xnanosleep(seconds)( ditemukan di gnulib ), ini pada gilirannya memanggil dtotimespec(seconds)( juga di gnulib ) untuk mengkonversi dari doubleke struct timespec.
  3. struct timespechanya sepasang angka: bagian integer (dalam detik) dan bagian fraksional (dalam nanodetik). Konversi tak terhingga positif ke bilangan bulat akan menghasilkan perilaku yang tidak terdefinisi (lihat §6.3.1.4 dari standar C), jadi alih-alih terpotong TYPE_MAXIMUM (time_t).
  4. Nilai aktual dari TYPE_MAXIMUM (time_t)tidak diatur dalam standar (bahkan sizeof(time_t)tidak); jadi, sebagai contoh mari kita ambil x86-64 dari kernel Linux baru-baru ini.

Ini ada TIME_T_MAXdi kernel Linux, yang didefinisikan ( time.h) sebagai:

(time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)

Perhatikan bahwa time_tini __kernel_time_tdan time_titu long; model data LP64 digunakan, demikian sizeof(long)juga 8 (64 bit).

Yang hasil dalam: TIME_T_MAX = 9223372036854775807.

Yaitu: sleep infinitemenghasilkan waktu tidur aktual 9223372036854775807 detik (10 ^ 11 tahun). Dan untuk sistem linux 32-bit ( sizeof(long)adalah 4 (32 bit)): 2147483647 detik (68 tahun; lihat juga tahun 2038 masalah ).


Sunting : rupanya nanosecondsfungsi yang dipanggil bukan secara langsung syscall, tetapi pembungkus yang tergantung pada OS (juga didefinisikan dalam gnulib ).

Ada langkah tambahan sebagai hasilnya: untuk beberapa sistem di mana HAVE_BUG_BIG_NANOSLEEPadalah truetidur tersebut dipotong menjadi 24 hari dan kemudian disebut dalam satu lingkaran. Ini adalah kasus untuk beberapa (atau semua?) Distro Linux. Perhatikan bahwa pembungkus ini tidak dapat digunakan jika uji konfigurasi- waktu berhasil ( sumber ).

Secara khusus, itu akan menjadi 24 * 24 * 60 * 60 = 2073600 seconds(ditambah 999999999 nanodetik); tapi ini disebut dalam satu lingkaran untuk menghormati total waktu tidur yang ditentukan. Oleh karena itu kesimpulan sebelumnya tetap valid.


Kesimpulannya, waktu tidur yang dihasilkan tidak terbatas tetapi cukup tinggi untuk semua tujuan praktis , bahkan jika selang waktu aktual yang dihasilkan tidak portabel; itu tergantung pada OS dan arsitekturnya.

Untuk menjawab pertanyaan awal, ini jelas cukup baik tetapi jika karena alasan tertentu (sistem yang sangat terbatas sumber daya) Anda benar-benar ingin menghindari penghitung waktu mundur tambahan yang tidak berguna, saya kira alternatif yang paling tepat adalah dengan menggunakan catmetode yang dijelaskan dalam jawaban lain. .

jp48
sumber
1
Dalam coreutils berikutnya, sleep infinitysekarang akan benar-benar tidur selamanya tanpa perulangan: lists.gnu.org/archive/html/bug-gnulib/2020-02/msg00081.html
Vladimir Panteleev
8

sleep infinityterlihat paling elegan, tetapi kadang-kadang itu tidak berhasil karena suatu alasan. Dalam hal ini, Anda dapat mencoba perintah pemblokiran lain seperti cat, read, tail -f /dev/null, grep adll

Hui Zheng
sumber
1
tail -f /dev/nulljuga merupakan solusi yang berfungsi untuk saya pada platform SaaS
schmunk
2
tail -f /dev/nulljuga memiliki kelebihan yaitu tidak mengonsumsi stdin. Saya telah menggunakannya untuk alasan itu.
Sudo Bash
Mereka yang mempertimbangkan opsi ini harus membaca jawaban ini untuk mengetahui konsekuensi dari opsi ini.
Bayangan
6

Bagaimana dengan mengirim SIGSTOP ke dirinya sendiri?

Ini harus menghentikan proses sampai SIGCONT diterima. Yang ada dalam kasus Anda: tidak pernah.

kill -STOP "$$";
# grace time for signal delivery
sleep 60;
michuelnik
sumber
6
Sinyal tidak sinkron. Jadi hal berikut dapat terjadi: a) shell calls kill b) kill memberitahu kernel bahwa shell akan menerima sinyal STOP c) kill terminate dan kembali ke shell d) shell berlanjut (mungkin berakhir karena skrip berakhir) e) kernel akhirnya menemukan waktu untuk mengirimkan sinyal STOP ke shell
not-a-user
1
@temple Wawasan luar biasa, tidak memikirkan sifat asinkron sinyal. Terima kasih!
michuelnik
4

Biarkan saya jelaskan mengapa sleep infinitybekerja meskipun tidak didokumentasikan. Jawaban jp48 juga berguna.

Hal yang paling penting: Dengan menentukan infatau infinity(keduanya tidak peka huruf besar-kecil), Anda dapat tidur untuk waktu terlama yang diizinkan oleh implementasi Anda (yaitu nilai lebih kecil dari HUGE_VALdan TYPE_MAXIMUM(time_t)).

Sekarang mari kita gali detailnya. Kode sumber sleepperintah dapat dibaca dari coreutils / src / sleep.c . Pada dasarnya, fungsi melakukan ini:

double s; //seconds
xstrtod (argv[i], &p, &s, cl_strtod); //`p` is not essential (just used for error check).
xnanosleep (s);

Pemahaman xstrtod (argv[i], &p, &s, cl_strtod)

xstrtod()

Menurut gnulib / lib / xstrtod.c , panggilan xstrtod()string yang dikonversi argv[i]ke nilai floating point dan menyimpannya *s, menggunakan fungsi konversi cl_strtod().

cl_strtod()

Seperti dapat dilihat dari coreutils / lib / cl-strtod.c , cl_strtod()mengonversi string ke nilai floating point, menggunakan strtod().

strtod()

Menurut man 3 strtod, strtod()mengkonversi string ke nilai tipe double. Kata halaman buku itu

Bentuk yang diharapkan dari string (bagian awal dari) adalah ... atau (iii) tak terhingga, atau ...

dan infinity didefinisikan sebagai

Infinity adalah "INF" atau "INFINITY", mengabaikan kasus.

Meskipun dokumen itu memberitahu

Jika nilai yang benar akan menyebabkan overflow, plus atau minus HUGE_VAL( HUGE_VALF, HUGE_VALL) dikembalikan

, tidak jelas bagaimana infinity diperlakukan. Jadi mari kita lihat kode sumber gnulib / lib / strtod.c . Yang ingin kita baca adalah

else if (c_tolower (*s) == 'i'
         && c_tolower (s[1]) == 'n'
         && c_tolower (s[2]) == 'f')
  {
    s += 3;
    if (c_tolower (*s) == 'i'
        && c_tolower (s[1]) == 'n'
        && c_tolower (s[2]) == 'i'
        && c_tolower (s[3]) == 't'
        && c_tolower (s[4]) == 'y')
      s += 5;
    num = HUGE_VAL;
    errno = saved_errno;
  }

Dengan demikian, INFdan INFINITY(keduanya case-insensitive) dianggap sebagai HUGE_VAL.

HUGE_VAL keluarga

Mari kita gunakan N1570 sebagai standar C. HUGE_VAL, HUGE_VALFdan HUGE_VALLmakro didefinisikan dalam §7.12-3

Makro
    HUGE_VAL
mengembang ke ekspresi konstan ganda yang positif, tidak selalu dianggap sebagai float. Makro
    HUGE_VALF
    HUGE_VALL
masing-masing mengambang dan analog ganda panjang HUGE_VAL.

HUGE_VAL,, HUGE_VALFdan HUGE_VALLdapat berupa infinitas positif dalam implementasi yang mendukung infinitas.

dan di §7.12.1-5

Jika hasil mengambang meluap dan standar pembulatan ini berlaku, maka fungsi mengembalikan nilai makro HUGE_VAL, HUGE_VALFatau HUGE_VALLsesuai dengan jenis kembali

Pemahaman xnanosleep (s)

Sekarang kami mengerti semua esensi xstrtod(). Dari penjelasan di atas, sangat jelas bahwa yang pertama xnanosleep(s)kita lihat sebenarnya berarti xnanosleep(HUGE_VALL).

xnanosleep()

Menurut kode sumber gnulib / lib / xnanosleep.c , xnanosleep(s)pada dasarnya melakukan ini:

struct timespec ts_sleep = dtotimespec (s);
nanosleep (&ts_sleep, NULL);

dtotimespec()

Fungsi ini mengonversi argumen tipe doubleke objek tipe struct timespec. Karena sangat sederhana, izinkan saya mengutip kode sumber gnulib / lib / dtotimespec.c . Semua komentar ditambahkan oleh saya.

struct timespec
dtotimespec (double sec)
{
  if (! (TYPE_MINIMUM (time_t) < sec)) //underflow case
    return make_timespec (TYPE_MINIMUM (time_t), 0);
  else if (! (sec < 1.0 + TYPE_MAXIMUM (time_t))) //overflow case
    return make_timespec (TYPE_MAXIMUM (time_t), TIMESPEC_HZ - 1);
  else //normal case (looks complex but does nothing technical)
    {
      time_t s = sec;
      double frac = TIMESPEC_HZ * (sec - s);
      long ns = frac;
      ns += ns < frac;
      s += ns / TIMESPEC_HZ;
      ns %= TIMESPEC_HZ;

      if (ns < 0)
        {
          s--;
          ns += TIMESPEC_HZ;
        }

      return make_timespec (s, ns);
    }
}

Karena time_tdidefinisikan sebagai tipe integral (lihat §7.27.1-3), wajar saja kita mengasumsikan nilai maksimum tipe time_tlebih kecil dari HUGE_VAL(tipe double), yang berarti kita memasuki case overflow. (Sebenarnya asumsi ini tidak diperlukan karena, dalam semua kasus, prosedurnya pada dasarnya sama.)

make_timespec()

Tembok terakhir yang harus kita panjat adalah make_timespec(). Sangat untungnya, mengutip kode sumber gnulib / lib / timespec.h sudah cukup sederhana.

_GL_TIMESPEC_INLINE struct timespec
make_timespec (time_t s, long int ns)
{
  struct timespec r;
  r.tv_sec = s;
  r.tv_nsec = ns;
  return r;
}
ynn
sumber
2

Baru-baru ini saya perlu melakukan ini. Saya datang dengan fungsi berikut yang akan memungkinkan bash tidur selamanya tanpa memanggil program eksternal apa pun:

snore()
{
    local IFS
    [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null ||
    {
        # workaround for MacOS and similar systems
        local fifo
        fifo=$(mktemp -u)
        mkfifo -m 700 "$fifo"
        exec {_snore_fd}<>"$fifo"
        rm "$fifo"
    }
    read ${1:+-t "$1"} -u $_snore_fd || :
}

CATATAN: Saya sebelumnya memposting versi ini yang akan membuka dan menutup deskriptor file setiap kali, tetapi saya menemukan bahwa pada beberapa sistem melakukan ini ratusan kali per detik pada akhirnya akan terkunci. Dengan demikian solusi baru membuat deskriptor file antara panggilan ke fungsi. Bash akan membersihkannya saat keluar.

Ini bisa disebut seperti / bin / sleep, dan itu akan tidur untuk waktu yang diminta. Disebut tanpa parameter, itu akan menggantung selamanya.

snore 0.1  # sleeps for 0.1 seconds
snore 10   # sleeps for 10 seconds
snore      # sleeps forever

Ada langganan dengan rincian berlebihan di blog saya di sini

baut
sumber
1

Pendekatan ini tidak akan menghabiskan sumber daya apa pun untuk menjaga proses tetap hidup.

while :; do sleep 1; done & kill -STOP $! && wait $!

Kerusakan

  • while :; do sleep 1; done & Membuat proses dummy di latar belakang
  • kill -STOP $! Menghentikan proses latar belakang
  • wait $! Tunggu proses latar belakang, ini akan memblokir selamanya, karena proses latar belakang dihentikan sebelumnya
qoomon
sumber
0

Alih-alih membunuh pengelola jendela, coba jalankan yang baru dengan --replaceatau -replacejika tersedia.

Dijeda sampai pemberitahuan lebih lanjut.
sumber
1
Jika saya menggunakan --replacesaya selalu mendapat peringatan seperti another window manager is already running. Bagi saya itu tidak masuk akal.
watain
-2
while :; do read; done

tidak perlu menunggu proses tidur anak.

shuaiming
sumber
1
Ini memakan stdinjika ini masih terhubung ke Internet tty. Jika Anda menjalankannya dengan < /dev/nullsibuk-loop. Mungkin bermanfaat dalam situasi tertentu, jadi saya tidak downvote.
Tino
1
Ini adalah ide yang sangat buruk, itu hanya akan memakan banyak cpu.
Mohammed Noureldin