barsju
barsju

Reputation: 4446

How to properly allocate memory for a pthread stack

I am trying to monitor the stack usage of my threads. To do that I need to know the address of the threads stack, and the only way I have found to do that is to set the stack using pthread_attr_setstack().

I'm currently using mmap to allocate the memory:

   pthread_attr_t ptAttr;
   pthread_t pth;
   pthread_attr_init(&ptAttr);
   void *stack = mmap(NULL, stksize, PROT_WRITE|PROT_READ, MAP_ANONYMOUS|MAP_SHARED, -1, 0);
   pthread_attr_setstack(&ptAttr, stack, stksize);
   pthread_create(&pth,&ptAttr,threadFunc,&info);

So first question, is this a good way of allocating the memory with mmap? Are the flags correct? Should I be using malloc instead? This will run on an low-resource device with no virtual/swap memory.

Second question, will this memory be released automatically when the thread dies? If you are not sure, is there a way to find out if it has been released?

Upvotes: 3

Views: 5704

Answers (1)

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215193

What do you mean by "monitor"? If you just want to ensure that not too much space is wasted for the stack (which would prevent you from having lots of threads on a 32-bit system or system with low ram+swap), you should simply use the pthread_attr_setstacksize function rather than pthread_attr_setstack. This way you are not responsible for allocating the stack yourself. You could also optionally use pthread_attr_setguardsize to ensure a larger zone of guard pages as protection if you're worried the thread will allocate more than one page at a time on the stack, but be aware that this will consume your virtual address space.

If you really want to measure stack usage, pthread_attr_setstack probably is the right tool, but it's not at all straightforward. I would allocate the memory with mmap, and make it read-only. Then install a SIGSEGV handler to mprotect the faulting page writable, increment a counter, and return. That will give you a count of the number of actual pages the thread touches. And since the signal handler will run in the faulting thread (this is guaranteed since it's a synchronous signal), you can keep the count in a thread-local storage variable to perform the counting on multiple threads.

You might actually need to make the last page or two writable before calling pthread_create, though, since the first write attempts will probably happen from the parent thread, and you probably don't want the signal handler running there if you're trying to store the results in thread-local storage.

To access your specific questions at the end:

  1. You do not want MAP_SHARED. That flag is for memory that will be shared between processes. It probably won't hurt in your case, but it's misleading. Use MAP_PRIVATE.

  2. The memory will not be released, and formally, can never be released. POSIX states quite explicitly that it's undefined behavior to ever reuse or free a stack given to a thread, since you cannot determine the lifetime reliably (even after pthread_join returns, it's conceptually possible that the thread is still executing its last few instructions to exit and thus still touching the stack, and it's possible that it remain stalled like that indefinitely long). I believe this is not possible on glibc/NPTL due to the way they use a kernel-generated futex wake event on thread exit to signal pthread_join atomically with the thread exit, but NPTL may cache and reuse stacks that you donated to a thread like this (since you're not allowed to reuse/free them yourself anyway). To be sure you'd have to check the source. As such, I would recommend NOT using pthread_attr_setstack at all in production code. Use pthread_attr_setstacksize. pthread_attr_setstack should be used only for development-time hacks like what you might be doing now.

Upvotes: 7

Related Questions