Bir sinyal işleyicide printf kullanmaktan nasıl kaçınılır?


87

Yana printfevresel değil, bir sinyal işleyici kullanmak için güvenli olması gerekiyordu değil. Ama kullanılan birçok örnek kod gördümprintf bu şekilde .

Öyleyse sorum şu: printfbir sinyal işleyicide kullanmaktan ne zaman kaçınmamız gerekiyor ve önerilen bir yedek var mı?


12
Başlığınızdaki soruya basit ve pek de kullanışlı olmayan bir cevap: printfŞu sinyal yönlendiricideki aramayı görüyor musunuz? Silin.
Keith Thompson

6
Merhaba Yu Hao! Okumayı çok ilginç bulacağınızı düşünüyorum. "Daha güvenli sinyal işleme için evresel işlevleri kullanın" Çok uzun zaman sonra okudum, burada suni olanı sizlerle paylaşmak istiyorum. Beğeneceğinizi umuyoruz.
Grijesh Chauhan

Yanıtlar:


58

Bazı bayrak değişkenlerini kullanabilir, bu bayrağı sinyal işleyicinin içinde ayarlayabilir ve bu bayrak çağrısı printf()işlevini main () veya normal çalışma sırasında programın başka bir bölümünde temel alabilirsiniz.

printfBir sinyal işleyicinin içinden olduğu gibi tüm işlevleri çağırmak güvenli değildir . Yararlı bir teknik, a'yı ayarlamak için bir sinyal işleyici kullanmak flagve bunu flag ana programdan kontrol etmek ve gerekirse bir mesaj yazdırmaktır.

Aşağıdaki örnekte dikkat edin, sinyal işleyici ding () alarm_fired, SIGALRM yakalandığında bir bayrağı 1'e ayarladı ve ana işlevde alarm_fireddeğer, koşullu olarak printf'i doğru şekilde çağırmak için incelendi.

Referans: Linux Programlamaya Başlamak, 4th Edition , Bu kitapta kodunuz tam olarak açıklanmıştır (ne isterseniz), Bölüm 11: İşlemler ve Sinyaller, sayfa 484

Ek olarak, işleyici işlevlerini yazarken özel dikkat göstermeniz gerekir çünkü bunlar eşzamansız olarak çağrılabilir. Yani, programın herhangi bir noktasında tahmin edilemeyen bir şekilde bir işleyici çağrılabilir. Çok kısa bir aralıkta iki sinyal gelirse, bir işleyici diğerinin içinde çalışabilir. Ve beyan etmek daha iyi bir uygulama olarak kabul edilir volatile sigatomic_t, bu tip her zaman atomik olarak erişilir, bir değişkene erişimi kesintiye uğratma konusundaki belirsizlikten kaçınır. ( Ayrıntılı açıklama için Atomik Veri Erişimi ve Sinyal İşleme'yi okuyun ).

Sinyal İşleyicileri Tanımlama : signal()veya sigaction()işlevleriyle kurulabilen bir sinyal işleyici işlevinin nasıl yazılacağını öğrenmek için okuyun . Kılavuz sayfasındaki
yetkili işlevlerin listesi, bu işlevi sinyal işleyici içinde çağırmak güvenlidir.


18
Bildirmek daha iyi bir uygulama olarak kabul edilirvolatile sigatomic_t alarm_fired;
Basile Starynkevitch


1
@GrijeshChauhan: Eğer bir ürün kodu üzerinde çalışıyorsak, duraklatma fonksiyonunu çağıramazız, sinyal oluştuğunda akış herhangi bir yerde olabilir, bu durumda "if (alarm_fired) printf (" Ding! \ n ");" kodda.
pankaj kushwaha

@pankajkushwaha evet, haklısınız, yarış durumundan muzdarip
Grijesh Chauhan

@GrijeshChauhan, Anlayamadığım iki şey var. 1. Bayrağı ne zaman kontrol edeceğinizi nasıl anlarsınız? Bu nedenle, hemen hemen her noktada yazdırılacak kodda birden fazla kontrol noktası olacak. 2. Sinyal kaydından önce sinyalin çağrılabileceği veya sinyalin kontrol noktasından sonra oluşabileceği kesinlikle yarış koşulları olacaktır. Bunun sadece bazı koşullarda baskıya yardımcı olacağını, ancak sorunu tamamen çözmediğini düşünüyorum.
Darshan b

55

Birincil sorun, sinyal kesintiye uğrarsa malloc()veya benzer bir işlev görürse, dahili durumun, boş ve kullanılmış liste veya diğer benzer işlemler arasında bellek bloklarını hareket ettirirken geçici olarak tutarsız olabilmesidir. Sinyal işleyicideki kod daha sonra başlatan bir işlevi çağırırsa malloc(), bu bellek yönetimini tamamen mahvedebilir.

C standardı, bir sinyal işleyicide neler yapabileceğinize dair oldukça muhafazakar bir görüş alır:

ISO / IEC 9899: 2011 §7.14.1.1 signalİşlev

¶5 Sinyal, abortveya raiseişlevinin çağrılmasının sonucu dışında meydana gelirse, sinyal işleyici statik veya iş parçacığı depolama süresi olan ve bir değer atama dışında kilit içermeyen atomik bir nesne olmayan herhangi bir nesneye başvurursa, davranış tanımsızdır. olarak bildirilen bir nesneye volatile sig_atomic_tveya sinyal işleyici, standart kitaplıkta abortişlev, _Exitişlev, quick_exitişlev veya işlev dışında herhangi bir işlevi çağırır signalve ilk bağımsız değişkenin çağrılmasına neden olan sinyale karşılık gelen sinyal numarasına eşittir. işleyici. Ayrıca, signalişlev için böyle bir çağrı bir SIG_ERRdönüşle sonuçlanırsa , değeri errnobelirsizdir. 252)

252) Eğer bir eşzamansız sinyal işleyici tarafından herhangi bir sinyal üretilirse, davranış tanımsızdır.

POSIX, bir sinyal işleyicide neler yapabileceğiniz konusunda çok daha cömerttir.

POSIX 2008 sürümündeki Signal Concepts şunları söylüyor:

İşlem çok iş parçacıklıysa veya işlem tek iş parçacıklıysa ve aşağıdakilerin sonucu dışında bir sinyal işleyici yürütülüyorsa:

  • İşlem arama abort(), raise(), kill(), pthread_kill(), ya da sigqueue()engellenmeyen bir sinyal üretmek için

  • Engeli kaldırılan ve engelini kaldıran çağrı geri dönmeden önce iletilen bekleyen sinyal

Sinyal işleyici errno, statik depolama süresi dışında herhangi bir nesneye atıfta bulunuyorsa, şu şekilde bildirilen bir nesneye bir değer atamak dışında davranış tanımsızdır .volatile sig_atomic_t veya sinyal işleyici, bu standartta tanımlanan işlevlerden biri dışında listelenen işlevlerden birini çağırıyorsa . aşağıdaki tablo.

Aşağıdaki tablo, eşzamansız sinyal güvenli olacak bir dizi işlevi tanımlamaktadır. Bu nedenle, uygulamalar bunları sinyal yakalama işlevlerinden kısıtlama olmaksızın çağırabilir:

Yukarıdaki tabloda yer almayan tüm işlevler, sinyaller açısından güvenli değildir. Sinyallerin varlığında, POSIX.1-2008'in bu hacmi tarafından tanımlanan tüm işlevler, tek bir istisna dışında, bir sinyal yakalama işlevinden çağrıldığında veya bu işlev tarafından kesintiye uğradığında tanımlandığı gibi davranacaktır: bir sinyal güvenli olmayan bir işlevi ve sinyal yakalama işlevi güvenli olmayan bir işlevi çağırır, davranış tanımsızdır.

Değeri elde eden errnoişlemler ve bir değer atayan işlemler errnoeşzamansız sinyal açısından güvenli olacaktır.

Bir iş parçacığına bir sinyal iletildiğinde, bu sinyalin eylemi sonlandırma, durdurma veya devam etmeyi belirtiyorsa, tüm süreç sırasıyla sonlandırılacak, durdurulacak veya devam ettirilecektir.

Bununla birlikte, printf()işlevler ailesi bu listede özellikle yoktur ve bir sinyal işleyiciden güvenli bir şekilde çağrılmayabilir.

POSIX 2016 güncelleme dahil güvenli fonksiyonların listesini uzanır, özellikle, işlevlerin çok sayıda <string.h>, özellikle değerli bir katkı (ya da özel olarak sinir bozucu bir gözetim idi). Liste şimdi:

Sonuç olarak, ya et al write()tarafından sağlanan biçimlendirme desteği olmadan kullanırsınız ya da printf()kodunuzun uygun yerlerinde (periyodik olarak) test ettiğiniz bir bayrak oluşturursunuz. Bu teknik hünerle gösterilmiştir cevap tarafından Grijesh Chauhan .


Standart C fonksiyonları ve sinyal güvenliği

chqrlie , kısmi bir cevabından fazlasına sahip olmadığım ilginç bir soru sorar :

Neden çoğu dizge işlevi <string.h>veya karakter sınıfı işlevlerinden <ctype.h>ve daha birçok C standart kitaplık işlevi yukarıdaki listede yer almıyor? Bir uygulamanın strlen(), bir sinyal işleyiciden aramayı güvensiz kılmak için kasıtlı olarak kötü olması gerekir .

İşlevlerin çoğu için <string.h>, niye ilan edilmedi zaman uyumsuz sinyal kasa görmek zor olduğunu ve hak verirdim strlen()birlikte bir örnektir strchr(), strstr()örneğin, vb Öte yandan, diğer fonksiyonlar strtok(), strcoll()ve strxfrm()oldukça karmaşıktır ve eşzamansız sinyal güvenli olma olasılığı düşüktür. Çünkü strtok()aramalar arasındaki durumu korur ve sinyal işleyici, kullanılan kodun bir kısmının strtok()karışıp karışmayacağını kolayca anlayamaz . strcoll()Ve strxfrm()işlevleri yerel duyarlı verilerle çalışmak ve yerel yükleme devlet ayarı her türlü kapsar.

Dan fonksiyonları (makrolar) <ctype.h>tüm yerel duyarlıdır ve bu nedenle aynı sorunlarla çalıştırabilir strcoll()ve strxfrm().

<math.h>Bir SIGFPE'den (kayan nokta istisnası) etkilenebildikleri için, matematiksel fonksiyonların neden eşzamansız sinyal güvenli olmadığını anlamakta zorlanıyorum , ancak bugünlerde bunlardan birini gördüğüm tek zaman tam sayı sıfıra bölüm. Benzer belirsizlik doğar <complex.h>, <fenv.h>ve <tgmath.h>.

İçindeki işlevlerden bazıları <stdlib.h>muaf tutulabilir - abs()örneğin. Diğerleri özellikle sorunludur malloc()ve aile en önemli örneklerdir.

POSIX ortamında kullanılan Standart C'deki (2011) diğer başlıklar için de benzer bir değerlendirme yapılabilir. (Standart C o kadar kısıtlayıcıdır ki, bunları saf bir Standart C ortamında analiz etmeye hiç ilgi yoktur.) 'Yerel ayara bağlı' olarak işaretlenenler güvensizdir çünkü yerel ayarları değiştirmek bellek tahsisi vb. Gerektirebilir.

  • <assert.h>- Muhtemelen güvenli değil
  • <complex.h>- Muhtemelen güvenli
  • <ctype.h> - Güvenli değil
  • <errno.h> - Güvenli
  • <fenv.h>- Muhtemelen güvenli değil
  • <float.h> - İşlev yok
  • <inttypes.h> - Yerel ayara duyarlı işlevler (güvenli değil)
  • <iso646.h> - İşlev yok
  • <limits.h> - İşlev yok
  • <locale.h> - Yerel ayara duyarlı işlevler (güvenli değil)
  • <math.h>- Muhtemelen güvenli
  • <setjmp.h> - Güvenli değil
  • <signal.h> - İzin verilir
  • <stdalign.h> - İşlev yok
  • <stdarg.h> - İşlev yok
  • <stdatomic.h>- Muhtemelen güvenli, muhtemelen güvenli değil
  • <stdbool.h> - İşlev yok
  • <stddef.h> - İşlev yok
  • <stdint.h> - İşlev yok
  • <stdio.h> - Güvenli değil
  • <stdlib.h> - Her şey güvenli değil (bazılarına izin verilir; bazılarına izin verilmez)
  • <stdnoreturn.h> - İşlev yok
  • <string.h> - Tamamen güvenli değil
  • <tgmath.h>- Muhtemelen güvenli
  • <threads.h>- Muhtemelen güvenli değil
  • <time.h>- Yerel ayara bağlıdır (ancak time()açıkça izin verilir)
  • <uchar.h> - Yerel ayara bağlı
  • <wchar.h> - Yerel ayara bağlı
  • <wctype.h> - Yerel ayara bağlı

POSIX başlıklarını analiz etmek… birçoğu olduğundan daha zordur ve bazı işlevler güvenli olabilir, ancak çoğu olmayacaktır… ama aynı zamanda daha basittir çünkü POSIX hangi işlevlerin eşzamansız sinyal güvenli olduğunu söyler (çoğu değil). Bir başlığın <pthread.h>üç güvenli işleve ve birçok güvensiz işleve sahip olduğuna dikkat edin .

Not: POSIX ortamındaki C işlevlerinin ve başlıklarının neredeyse tüm değerlendirmeleri yarı eğitimli bir tahmindir. Bir standartlar kurumundan kesin bir ifade vermenin anlamı yoktur.


Neden çoğu dizge işlevi <string.h>veya karakter sınıfı işlevi <ctype.h>ve daha birçok C standart kitaplık işlevi yukarıdaki listede yer almıyor? Bir uygulamanın strlen(), bir sinyal işleyiciden aramayı güvensiz kılmak için kasıtlı olarak kötü olması gerekir .
chqrlie

@chqrlie: ilginç soru - güncellemeye bakın (bu kadarını yorumlara mantıklı bir şekilde sığdırmanın bir yolu yoktu).
Jonathan Leffler

Derinlemesine analiziniz için teşekkür ederiz. Konuyla ilgili olarak <ctype.h>, yerel ayara özgüdür ve sinyal bir yerel ayar işlevini kesintiye uğratırsa sorunlara neden olabilir, ancak yerel ayar yüklendikten sonra bunları kullanmak güvenli olmalıdır. Tahminimce, bazı karmaşık durumlarda, yerel verilerin yüklenmesi aşamalı olarak yapılabilir, bu da işlevleri <ctype.h>güvensiz hale getirir . Sonuç kalır: Şüphe duyduğunuzda çekimser kalın.
chqrlie

@chqrlie: Ben hikayenin ruhu olması gerektiğini kabul ediyoruz Şüphe, çekimser zaman . Bu güzel bir özet.
Jonathan Leffler

13

printfBir sinyal işleyicide kullanmaktan nasıl kaçınılır ?

  1. Her zaman kaçının, diyecek ki: printf()Sinyal tutucularda kullanmayın .

  2. En azından POSIX uyumlu sistemlerde write(STDOUT_FILENO, ...)bunun yerine kullanabilirsiniz printf(). Ancak biçimlendirme kolay olmayabilir: Yazma veya eşzamansız güvenlik işlevlerini kullanarak sinyal işleyiciden int yazdırın


1
Alk Always avoid it.demek? Kaçınmak printf()mı?
Grijesh Chauhan

2
@GrijeshChauhan: Evet, OP printf()sinyal işleyicilerini ne zaman kullanmaktan kaçınması gerektiğini soruyordu .
alk

Puan için Alk +1 , sinyal işleyicilerinde kullanmaktan nasıl kaçınılacağını 2soran OP'ye bakın. printf()
Grijesh Chauhan

7

Hata ayıklama amacıyla, aslında yalnızca async-signal-safelistedeki işlevleri çağırdığınızı doğrulayan ve bir sinyal bağlamında çağrılan her güvenli olmayan işlev için bir uyarı mesajı yazdıran bir araç yazdım . Bir sinyal bağlamından eşzamansız olmayan işlevleri çağırma sorununu çözmese de, en azından yanlışlıkla yaptığınız durumları bulmanıza yardımcı olur.

Kaynak kodu GitHub üzerindedir . Aşırı signal/sigactionyükleyerek çalışır , ardından PLTgüvenli olmayan işlevlerin girişlerini geçici olarak ele geçirir ; bu, güvenli olmayan işlevlere yapılan çağrıların bir sarmalayıcıya yeniden yönlendirilmesine neden olur.



1

Bir seçme döngüsüne sahip programlarda özellikle yararlı olan bir teknik , bir sinyal alındığında bir borunun altına tek bir bayt yazmak ve ardından sinyali seçme döngüsünde işlemektir. Bu satırlar boyunca bir şeyler (hata işleme ve diğer ayrıntılar kısa olması için atlanmıştır) :

Hangi sinyal olduğunu önemsiyorsanız , borudaki bayt sinyal numarası olabilir.


1

Kendi eşzamansız sinyal güvenliğinizi uygulayın snprintf("%dve kullanınwrite

Düşündüğüm kadar kötü değil , C'deki bir int dizgeye nasıl dönüştürülür? birkaç uygulamaya sahiptir.

Sinyal işleyicilerin erişebileceği yalnızca iki ilginç veri türü olduğundan:

  • sig_atomic_t küreseller
  • int sinyal argümanı

bu temelde tüm ilginç kullanım durumlarını kapsar.

Aynı strcpyzamanda sinyal güvenliğinin olması, işleri daha da iyi hale getirir.

Aşağıdaki POSIX programı, ile tetikleyebileceğiniz SIGINT şimdiye kadar kaç kez aldığını Ctrl + Cve ve sinyal kimliğini standart olarak yazdırır .

Ctrl + \(SIGQUIT) ile programdan çıkabilirsiniz .

main.c:

Derleyin ve çalıştırın:

Ctrl + C'ye on beş kez bastıktan sonra terminal şunu gösterir:

2sinyal numarası nerede SIGINT.

Ubuntu 18.04'te test edilmiştir. GitHub yukarı akış .


0

write()Asenkron sinyal güvenli bir işlev olan doğrudan da kullanabilirsiniz .


-1

Pthread kitaplığını kullanıyorsanız, sinyal işleyicilerinde printf'i kullanabilirsiniz. unix / posix, printf'in iş parçacıkları için atomik olduğunu belirtir, çünkü Dave Butenhof buradan yanıt verin: https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqw Daha net bir resim elde etmek için unutmayın Printf çıktısı için, uygulamanızı GUI tarafından oluşturulan sözde-tty yerine bir konsolda çalıştırmalısınız (linux'ta konsol 1'i başlatmak için ctl + alt + f1 kullanın).


3
Sinyal işleyicileri ayrı bir iş parçacığında çalışmazlar, sinyal kesilmesi oluştuğunda çalışan iş parçacığı bağlamında çalışırlar. Bu cevap tamamen yanlış.
kaşıntı
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.