ในerrno.h
ตัวแปรนี้มีการประกาศตามextern int errno;
คำถามของฉันคือมันปลอดภัยที่จะตรวจสอบerrno
ค่าหลังจากการโทรหรือใช้ perror () ในรหัสแบบมัลติเธรด นี่เป็นตัวแปรที่ปลอดภัยหรือไม่ ถ้าไม่เช่นนั้นทางเลือกคืออะไร?
ฉันใช้ linux กับ gcc บนสถาปัตยกรรม x86
ในerrno.h
ตัวแปรนี้มีการประกาศตามextern int errno;
คำถามของฉันคือมันปลอดภัยที่จะตรวจสอบerrno
ค่าหลังจากการโทรหรือใช้ perror () ในรหัสแบบมัลติเธรด นี่เป็นตัวแปรที่ปลอดภัยหรือไม่ ถ้าไม่เช่นนั้นทางเลือกคืออะไร?
ฉันใช้ linux กับ gcc บนสถาปัตยกรรม x86
คำตอบ:
ใช่มันปลอดภัยสำหรับเธรด บน Linux ตัวแปรโกลบอล errno เป็นแบบเฉพาะเธรด POSIX ต้องการให้ errno นั้นเป็น threadsafe
ดูhttp://www.unix.org/whitepapers/reentrant.html
ใน POSIX.1 errno ถูกกำหนดเป็นตัวแปรโกลบอลภายนอก แต่คำจำกัดความนี้ไม่สามารถยอมรับได้ในสภาพแวดล้อมแบบมัลติเธรดเนื่องจากการใช้งานอาจส่งผลให้เกิดผลลัพธ์แบบไม่เป็นทางการ ปัญหาคือเธรดตั้งแต่สองเธรดขึ้นไปสามารถพบข้อผิดพลาดได้ทั้งหมดทำให้มีการตั้งค่า errno เดียวกัน ภายใต้สถานการณ์เหล่านี้เธรดอาจสิ้นสุดการตรวจสอบ errno หลังจากเธรดอื่นได้รับการอัพเดตแล้ว
หากต้องการหลีกเลี่ยง nondeterminism ที่เกิดขึ้น POSIX.1c นิยามใหม่ errno เป็นบริการที่สามารถเข้าถึงหมายเลขข้อผิดพลาดต่อเธรดดังนี้ (ISO / IEC 9945: 1-1996, §2.4):
บางฟังก์ชั่นอาจมีหมายเลขข้อผิดพลาดในตัวแปรที่เข้าถึงผ่าน errno สัญลักษณ์ errno สัญลักษณ์ถูกกำหนดโดยการรวมส่วนหัวตามที่ระบุโดย C Standard ... สำหรับแต่ละเธรดของกระบวนการค่าของ errno จะไม่ได้รับผลกระทบจากการเรียกใช้ฟังก์ชันหรือการมอบหมายให้ errno โดยเธรดอื่น
ดูhttp://linux.die.net/man/3/errno ด้วย
errno เป็นเธรดแบบโลคัล การตั้งค่าในหนึ่งเธรดไม่ส่งผลกระทบต่อค่าในเธรดอื่น ๆ
# if !defined _LIBC || defined _LIBC_REENTRANT
_LIBC ไม่ได้ถูกกำหนดไว้เมื่อรวบรวมโปรแกรมปกติ อย่างไรก็ตามให้รันเสียงสะท้อน#include <errno.h>' | gcc -E -dM -xc -
และดูความแตกต่างด้วยและไม่มี -pthread errno อยู่#define errno (*__errno_location ())
ในทั้งสองกรณี
Errno ไม่ใช่ตัวแปรง่ายๆอีกต่อไปมันเป็นสิ่งที่ซับซ้อนอยู่เบื้องหลังโดยเฉพาะเพื่อให้ปลอดภัยต่อเธรด
ดู$ man 3 errno
:
ERRNO(3) Linux Programmer’s Manual ERRNO(3)
NAME
errno - number of last error
SYNOPSIS
#include <errno.h>
DESCRIPTION
...
errno is defined by the ISO C standard to be a modifiable lvalue of
type int, and must not be explicitly declared; errno may be a macro.
errno is thread-local; setting it in one thread does not affect its
value in any other thread.
เราสามารถตรวจสอบอีกครั้ง:
$ cat > test.c
#include <errno.h>
f() { g(errno); }
$ cc -E test.c | grep ^f
f() { g((*__errno_location ())); }
$
ใน errno.h ตัวแปรนี้ถูกประกาศเป็น extern int ภายนอก
นี่คือสิ่งที่มาตรฐาน C บอกว่า:
แมโคร
errno
ไม่จำเป็นต้องเป็นตัวระบุของวัตถุ มันอาจขยายเป็น lvalue ที่แก้ไขได้ซึ่งเป็นผลมาจากการเรียกใช้ฟังก์ชัน (ตัวอย่างเช่น*errno()
)
โดยทั่วไปแล้วerrno
เป็นแมโครที่เรียกใช้ฟังก์ชันที่ส่งคืนที่อยู่ของหมายเลขข้อผิดพลาดสำหรับเธรดปัจจุบันจากนั้นยกเลิกการกำหนด
นี่คือสิ่งที่ฉันมีบน Linux ใน /usr/include/bits/errno.h:
/* Function to get address of global `errno' variable. */
extern int *__errno_location (void) __THROW __attribute__ ((__const__));
# if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value. */
# define errno (*__errno_location ())
# endif
ในท้ายที่สุดมันสร้างรหัสประเภทนี้:
> cat essai.c
#include <errno.h>
int
main(void)
{
errno = 0;
return 0;
}
> gcc -c -Wall -Wextra -pedantic essai.c
> objdump -d -M intel essai.o
essai.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
0: 55 push ebp
1: 89 e5 mov ebp,esp
3: 83 e4 f0 and esp,0xfffffff0
6: e8 fc ff ff ff call 7 <main+0x7> ; get address of errno in EAX
b: c7 00 00 00 00 00 mov DWORD PTR [eax],0x0 ; store 0 in errno
11: b8 00 00 00 00 mov eax,0x0
16: 89 ec mov esp,ebp
18: 5d pop ebp
19: c3 ret
ในระบบ Unix หลายระบบการคอมไพล์ด้วยการ-D_REENTRANT
ทำให้แน่ใจว่าerrno
ปลอดภัยต่อเธรด
ตัวอย่างเช่น:
#if defined(_REENTRANT) || _POSIX_C_SOURCE - 0 >= 199506L
extern int *___errno();
#define errno (*(___errno()))
#else
extern int errno;
/* ANSI C++ requires that errno be a macro */
#if __cplusplus >= 199711L
#define errno errno
#endif
#endif /* defined(_REENTRANT) */
-D_REENTRANT
ผมคิดว่าคุณไม่ได้มีการรวบรวมรหัสอย่างชัดเจนด้วย โปรดอ้างอิงการอภิปรายเกี่ยวกับคำตอบอื่น ๆ สำหรับคำถามเดียวกัน
-D_XOPEN_SOURCE=500
-D_XOPEN_SOURCE=600
ไม่ใช่ทุกคนที่รบกวนจิตใจเพื่อให้แน่ใจว่ามีการระบุสภาพแวดล้อม POSIX แล้ว-D_REENTRANT
สามารถบันทึกเบคอนของคุณได้ แต่คุณยังต้องระวังในแต่ละแพลตฟอร์มเพื่อให้แน่ใจว่าคุณได้รับพฤติกรรมที่ต้องการ
errno
สิ่งใดที่ขยายเป็น lvalue ที่แก้ไขได้ (201) ซึ่งมีint
ระยะเวลาการเก็บข้อมูลแบบโลคัลชนิดและเธรดค่าที่ตั้งค่าเป็นหมายเลขข้อผิดพลาดเชิงบวกโดยฟังก์ชันไลบรารีต่างๆ หากคำจำกัดความของแมโครถูกระงับเพื่อเข้าถึงวัตถุจริงหรือโปรแกรมกำหนดตัวระบุด้วยชื่อerrno
พฤติกรรมจะไม่ได้กำหนด [... ดำเนินการต่อ ... ]
errno
ไม่จำเป็นต้องเป็นตัวบ่งชี้ของวัตถุ มันอาจขยายเป็น lvalue ที่แก้ไขได้ซึ่งเป็นผลมาจากการเรียกใช้ฟังก์ชัน (ตัวอย่างเช่น*errno()
) ข้อความหลักยังคงดำเนินต่อไป: ค่าของ errno ในเธรดเริ่มต้นเป็นศูนย์เมื่อเริ่มต้นโปรแกรม (ค่าเริ่มต้นของ errno ในเธรดอื่นเป็นค่าที่ไม่แน่นอน) แต่ไม่เคยถูกตั้งค่าเป็นศูนย์โดยฟังก์ชันไลบรารีใด ๆ POSIX ใช้มาตรฐาน C99 ซึ่งไม่รู้จักเธรด [... ยังดำเนินการต่อ ... ]
นี่คือจาก<sys/errno.h>
บน Mac ของฉัน:
#include <sys/cdefs.h>
__BEGIN_DECLS
extern int * __error(void);
#define errno (*__error())
__END_DECLS
ดังนั้นในขณะนี้คือฟังก์ชั่นerrno
__error()
มีการใช้งานฟังก์ชันเพื่อให้มีความปลอดภัยต่อเธรด
ใช่ตามที่อธิบายไว้ในหน้า man errnoและคำตอบอื่น ๆ errno เป็นตัวแปรโลคัลเธรด
อย่างไรก็ตามมีรายละเอียดโง่ ๆ ซึ่งอาจลืมได้ง่าย โปรแกรมควรบันทึกและกู้คืน errno บนตัวจัดการสัญญาณใด ๆ ที่เรียกใช้การเรียกใช้ระบบ นี่เป็นเพราะสัญญาณจะถูกจัดการโดยหนึ่งในเธรดกระบวนการซึ่งสามารถเขียนทับค่าของมัน
ดังนั้นตัวจัดการสัญญาณควรบันทึกและเรียกคืน errno สิ่งที่ต้องการ:
void sig_alarm(int signo)
{
int errno_save;
errno_save = errno;
//whatever with a system call
errno = errno_save;
}
ฉันคิดว่าคำตอบคือ "มันขึ้นอยู่กับ" ไลบรารีรันไทม์ C ที่ปลอดภัยสำหรับเธรดมักจะใช้ errno เป็นการเรียกใช้ฟังก์ชัน (แมโครขยายไปยังฟังก์ชัน) หากคุณกำลังสร้างโค้ดเธรดด้วยค่าสถานะที่ถูกต้อง
เราสามารถตรวจสอบได้ด้วยการรันโปรแกรมอย่างง่าย ๆ บนเครื่อง
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#define NTHREADS 5
void *thread_function(void *);
int
main()
{
pthread_t thread_id[NTHREADS];
int i, j;
for(i=0; i < NTHREADS; i++)
{
pthread_create( &thread_id[i], NULL, thread_function, NULL );
}
for(j=0; j < NTHREADS; j++)
{
pthread_join( thread_id[j], NULL);
}
return 0;
}
void *thread_function(void *dummyPtr)
{
printf("Thread number %ld addr(errno):%p\n", pthread_self(), &errno);
}
เรียกใช้โปรแกรมนี้และคุณสามารถดูที่อยู่ที่แตกต่างกันสำหรับ errno ในแต่ละเธรด ผลลัพธ์ของการทำงานบนเครื่องของฉันดูเหมือนว่า: -
Thread number 140672336922368 addr(errno):0x7ff0d4ac0698
Thread number 140672345315072 addr(errno):0x7ff0d52c1698
Thread number 140672328529664 addr(errno):0x7ff0d42bf698
Thread number 140672320136960 addr(errno):0x7ff0d3abe698
Thread number 140672311744256 addr(errno):0x7ff0d32bd698
ขอให้สังเกตว่าที่อยู่จะแตกต่างกันสำหรับกระทู้ทั้งหมด