From APUE, I learned about guardsize and stackaddr of a stack. If a rsp pointer is going to be lower than stackaddr, the rsp enters the guard stack area, and a signal emits to notify the program.
I am wondering if it is possible to implement dynamic–grow(which grows dynamically) stack using this feature. Can you show
how?
The stack can be grown "dynamically"
Set up an alternate signal stack.
Set up signal handler for SIGSEGV with SA_ONSTACK to use the alternate stack.
When the program runs out of stack, it will be issued a SIGSEGV signal.
The signal handler can use getrlimit/setrlimit with RLIMIT_STACK to change the stack size.
getrlimit and setrlimit are not explicitly mentioned in man signal-safety but I don't see why they would be a problem in a signal handler.
Here is a sample test program. It is annotated. Invoke with -d to dynamically increase the stack size from within the signal handler:
// stacktest.c -- test dynamically growing stack
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <setjmp.h>
#include <sys/time.h>
#include <sys/resource.h>
volatile int opt_t = 0;
volatile int opt_d = 0;
volatile int opt_i = 0;
void *top; // initial top of stack
// signal handler
volatile int may_grow = 1; // grow stack within signal handler
volatile int err1 = 0; // error on get
volatile int err2 = 0; // error on set
volatile int hitno; // number of signals
jmp_buf jbuf;
// alternate signal stack
unsigned char altstk[64 * 4096] __attribute__((aligned(4096)));
// xprtstr -- print string in signal handler
void
xprtstr(const char *str)
{
size_t len = strlen(str);
write(1,str,len);
}
// xprtstr -- print number in signal handler
void
xprtnum(unsigned long val,const char *sym)
{
static const char *hex = "0123456789ABCDEF";
xprtstr(" ");
xprtstr(sym);
xprtstr("=");
char buf[100];
char *bp = &buf[50];
*bp-- = 0;
for (int idx = 0; idx < 16; ++idx, --bp) {
*bp = hex[val & 0x0F];
val >>= 4;
}
++bp;
xprtstr(bp);
}
// sigfault -- SIGSEGV handler
void
sigfault(int signo,siginfo_t *info,void *vp)
{
++hitno;
xprtstr("ISR hit");
xprtnum(signo,"signo");
xprtnum(hitno,"hitno");
xprtstr("\n");
struct rlimit rlim;
do {
if (opt_t) {
xprtstr("ISR test\n");
siglongjmp(jbuf,1);
break;
}
// dynamically grow the stack
if (may_grow) {
xprtstr("ISR grow\n");
may_grow = 0;
err1 = getrlimit(RLIMIT_STACK,&rlim);
xprtnum(rlim.rlim_cur,"rlim_cur");
xprtnum(rlim.rlim_cur / 1024,"rlim_cur");
xprtstr("\n");
rlim.rlim_cur += 8 * 1024 * 1024;
err2 = setrlimit(RLIMIT_STACK,&rlim);
getrlimit(RLIMIT_STACK,&rlim);
xprtnum(rlim.rlim_cur,"rlim_cur");
xprtnum(rlim.rlim_cur / 1024,"rlim_cur");
xprtstr("\n");
break;
}
// stop the program
xprtstr("ISR stop\n");
siglongjmp(jbuf,2);
} while (0);
}
// loop -- recursive function to overflow stack
void
loop(unsigned char *old)
{
// get some space on the stack
unsigned char cur[4096];
// get current stack frame address
void *frame = __builtin_frame_address(0);
// get amount of space used on stack
size_t dif = top - frame;
// show where we are
printf("loop: top=%p frame=%p dif=%8.8zX/%zu may_grow=%d\n",
top,frame,dif,dif / 1024,may_grow);
// keep consuming more stack
// NOTE: we don't actually use cur/old but ensure that the compiler won't
// optimize it away
loop(cur);
}
#define SHOWFLAGS(_msk) \
if (flags & (_msk)) \
printf(" " #_msk)
void
showsa(const struct sigaction *sa,const char *who)
{
int flags = sa->sa_flags;
// show the signal flags
printf("showflags: %s flags=%8.8X",who,flags);
SHOWFLAGS(SA_NOCLDSTOP);
SHOWFLAGS(SA_NOCLDWAIT);
SHOWFLAGS(SA_NODEFER);
SHOWFLAGS(SA_ONSTACK);
SHOWFLAGS(SA_RESETHAND);
SHOWFLAGS(SA_RESTART);
SHOWFLAGS(SA_SIGINFO);
// show the signal mask
for (int signo = 1; signo < 32; ++signo) {
if (sigismember(&sa->sa_mask,signo))
printf(" S%d",signo);
}
printf("\n");
}
// setup_signal -- set up signal handler
void
setup_signal(int signo)
{
struct sigaction sa;
assert(sigaction(signo,NULL,&sa) == 0);
sa.sa_sigaction = sigfault;
showsa(&sa,"BEF");
sa.sa_flags |= SA_SIGINFO;
sa.sa_flags |= SA_ONSTACK;
sa.sa_flags &= ~SA_RESETHAND;
assert(sigaction(signo,&sa,NULL) == 0);
assert(sigaction(signo,NULL,&sa) == 0);
showsa(&sa,"AFT");
}
// setup_altstack -- set up alternate signal stack
void
setup_altstack(void)
{
stack_t ss;
sigaltstack(NULL,&ss);
printf("setup_altstack: ss_sp=%p ss_flags=%8.8X ss_size=%zu\n",
ss.ss_sp,ss.ss_flags,ss.ss_size);
void *sp = altstk;
sp += 16 * 4096;
printf("altstk=%p sp=%p\n",altstk,sp);
ss.ss_sp = sp;
ss.ss_size = sizeof(altstk) / 2;
ss.ss_flags = SS_ONSTACK;
assert(sigaltstack(&ss,NULL) == 0);
sigaltstack(NULL,&ss);
printf("setup_altstack: ss_sp=%p ss_flags=%8.8X ss_size=%zu\n",
ss.ss_sp,ss.ss_flags,ss.ss_size);
if (ss.ss_sp != sp) {
printf("setup_altstack: failed\n");
exit(99);
}
}
#define SHOWOPT(_opt,_reason) \
if (_opt) \
printf(#_opt " -- " _reason)
int
main(int argc,char **argv)
{
setlinebuf(stdout);
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
char *cp = *argv;
if (*cp != '-')
break;
cp += 2;
switch (cp[-1]) {
case 'd':
opt_d = ! opt_d;
break;
case 'i':
opt_i = ! opt_i;
break;
case 't':
opt_t = ! opt_t;
break;
}
}
// show the options
SHOWOPT(opt_d,"dynamically grow stack within signal handler");
SHOWOPT(opt_i,"grow stack initially");
SHOWOPT(opt_t,"test sigsetjmp/siglongjmp");
// set up alternate signal stack and signal handler
if (opt_d || opt_t) {
setup_altstack();
setup_signal(SIGSEGV);
}
// test our sigsetjmp/siglongjmp
if (opt_t) {
for (int try = 1; try <= 2; ++try) {
if (! sigsetjmp(jbuf,1)) {
printf("main: ptr try=%d\n",try);
unsigned int *ptr = NULL;
*ptr = 23;
}
else
printf("main: resume try=%d\n",try);
}
opt_t = 0;
}
// set up large stack outside of signal handler
if (opt_i) {
struct rlimit rlim;
getrlimit(RLIMIT_STACK,&rlim);
printf("rlim_cur=%lu/%lu\n",rlim.rlim_cur,rlim.rlim_cur / 1024);
rlim.rlim_cur = 32 * 1024 * 1024;
setrlimit(RLIMIT_STACK,&rlim);
getrlimit(RLIMIT_STACK,&rlim);
printf("rlim_cur=%lu/%lu\n",rlim.rlim_cur,rlim.rlim_cur / 1024);
}
printf("main: loop\n");
// dynamically grow the stack
may_grow = opt_d;
// top of stack
top = __builtin_frame_address(0);
if (! sigsetjmp(jbuf,1))
loop(NULL);
else
printf("main: stop\n");
return 0;
}
UPDATE:
This describes how to make "the" stack grow dynamically (very interesting!), but I take the question to be about the stacks of threads other than the initial one, or at least inclusive of those stacks. I don't think this answer addresses them. –
John Bollinger
Although the question was tagged with pthreads, I'm not sure if OP actually was talking about subthreads doing this.
I'm not sure that this can be done for subthreads (created via pthread_create). At least not by using a default pthread_create call.
Without special pthread_attr_t values, the default for pthread_create is to malloc a stack [of a default size]. With attributes, the caller might set a larger stack size. And/or the caller will [usually] do an explicit malloc and pass the address along (with the size).
AFAICT, from reading glibc source, pthread_create will not set up a guard area if the user provides a stack pointer. Setting up the guard area [if there is to be one] is the responsibility of the caller.
But, in either case, if the stack overflows (with a guard area), a signal will be generated (SIGSEGV ?).
But, what can one do at that point???
The [pthread internal] function that calls the user's start_routine, the start_routine itself, and any functions that the start routine has called, already have pointers to things on that "old" stack [we must assume this].
So, the per-thread stack can not be moved (i.e. no realloc).
The only way to [possibly] do this is for the caller to provide an explicit stack pointer [and size]. As mentioned, the caller must set up the guard pages (via mprotect, I assume).
Although userfaultfd et. al. might be usable/preferable, I'm going to assume that the caller must use an explicit mmap call (vs. using the heap malloc/realloc).
The main thread's stack [generally] grows downward from the top of the virtual memory space. It can grow until all physical memory is used up and the paging disk is full.
However, for a per-thread (subthread) stack, the caller must decide on the maximum per-thread stack size before creating the thread. More on this below.
It can set up a stack (via mmap) of a smaller initial size. Once thread is created, the base/top stack address must remain constant.
Edit: The following is modified by additional thoughts below.
If a fault occurs, the signal handler could try an extension of the stack. There may be a few ways to do this. My best guess for this is:
It may have to copy/save the existing stack data [somewhere ;-)] if the remap below does not copy the existing data the way realloc does.
Temporarily undo any guard pages.
unmap/remap the stack at the same address but with a larger size (via mremap and/or mmap using MREMAP_FIXED/MAP_FIXED).
If necessary, copy back the stack data onto the "new" stack.
Set up new guard pages.
return from the signal handler [and hope ;-)].
As I said, subthread stacks can't grow "infinitely" as the main thread stack can (via setrlimit).
The sizes/addresses in the example below are not "to scale" ...
Consider two threads (e.g. tA and tB) that start with a small size:
tA's stack at xxxx1000 with size 1000
tB's stack at xxxx2000 with size 1000
If tB hits its guard page and faults, what happens? It has no room the extend its stack downward without colliding with the top of tA's stack.
So, we must map the stacks with sufficient space to grow to the "maximum" and we must know that beforehand.
We need to "space out" the stack addresses so they have room to grow [even if not all pages are mapped initially].
Let's assume that the "maximum" size is 10000. A mapping that would work is:
tA's stack at xxxx1000 with size 1000
tB's stack at xxx11000 with size 1000
Now, if tB hits its guard page, it can extend its stack up to the maximum of 10000
Additional thoughts ...
We probably must initially map the entire maximum region. Otherwise, other unrelated mmap calls may grab space in the proposed extension area. Either mmap from the heap manager, shared memory mappings, or even mmap calls done when setting up other threads.
So, we don't need to use mremap to ever increase the size of the region. Rather, we should use madvise with MADV_DONTNEED on the area that we're not currently using.
If we do this, we don't need a signal handler or guard pages to invoke it to extend the area. Just doing MADV_DONTNEED will keep resource usage low(er).
The thread function can (after popping the stack a bit) release the "popped" area with MADV_DONTNEED.
Other MADV_* options might be better.
All of the above strikes me as craziness! The only use would be a [hugely] multithreaded app that is doing deeply recursive functions.
But, if that's the case, it would be better to convert the recursive functions into ones that don't use actual program stack based recursion. Rather, the function manages an array of structs as a software controlled [pseudo] "stack". Each struct has all the variables that were function/automatic scope in the program stack frame.
The size of this array can be controlled with realloc. Or, we can implement the "stack" as a linked list of these structs with a "slab" allocation scheme. With the slab scheme, all threads could share the same slab allocator. This has the advantage that the amount of memory required can be smaller than if each thread had its own/private slab allocator.
So, IMO, don't do this on the real, per-thread program stack!
Okay, so that's what I've come up with. I've not written code for it or tested it. That's an exercise I think I'll leave to the reader ;-)
Related
Suppose this function runs indefinitely in a separate thread.
I need to change the local servers array size from 1024 bytes to 4096.
void * threadFunc(void * arg)
{
char servers[ 1024 ];
for (;;)
{
// Do stuff with servers, then sleep...
sleep(2);
}
}
However, it's unsettling to keep growing that array size since the default stack reservation size of the thread is generally 1MB. I'm considering these two options
OPTION 1
Update the array from 1024 to 4096
void * threadFunc(void * arg)
{
char servers[ 4096 ];
for (;;)
{
// Do stuff with servers, then sleep...
sleep(2);
}
}
OPTION 2
Replace the local array with malloc()
void * threadFunc(void * arg)
{
char* servers;
servers = (char*)malloc(4096);
for (;;)
{
// Do stuff with servers, then sleep...
sleep(2);
}
}
I'm leaning towards Option 1 because it's an easy change and the array should not grow much larger in the future.
Choosing Option 2 is more complicated because it involves these scenarios:
The malloc might fail so abort the thread.
When to call free() to release the memory? Cannot call free() within the infinite loop as it's always needed. Cannot call free() after the loop because it's dead code which will never be reached. Also, if the thread crashes, maybe there's a memory leak?
Question
In general, when is a local stack array too large and should be replaced with malloc?
I assume Option 1 makes the most sense for the specific function?
I have the following assignment for an online class and was wondering if anyone was familiar with paranoid arrays, as it's very difficult to get help for this specific class.
Your paranoid array will expose a very simple interface provided in parray.h. You can
assume the parray will not be free’d. The parray new call creates an array of a set-number
of entries (count argument) of a fixed size (size argument). Internally, the parray will
not arrange the elements consecutively in memory, so the parray entry function returns a
pointer to a given entry (specified by argument index).
In order to trigger a segfault upon overflow within an element, you should use guard
pages. A guard page’s main purpose is to trigger segfaults when it is accessed. Thus, pagetable read, write, and execute permissions on a guard table are disabled, so any access to
the page will trigger the fault. When a guard page is placed immediately after a buffer or
data structure, any buffer overflow bugs affecting that piece of memory will hit the guard
page, triggering an instant segfault
Every entry in your array should be bounded by guard pages on each side. For example,
an array with 10 entries should use 11 guard pages: one before the first entry, one after the
last entry, and nine in between consecutive entries.
My parray_new and parray_entry call is as follows:
typedef char byte;
parray_t* parray_new(int size, int count)
{
struct parray* p = NULL;
// TODO: Allocate and return parray
// Add guard pages first at this time
int pagesize = getpagesize();
p->size = (size * count) + (pagesize * count) + pagesize;
p->array = malloc(p->size + pagesize - 1);
if(posix_memalign(&p->array, p->size, count))
{
exit(0);
}
return p;
}
void* parray_entry(struct parray* p, int index)
{
//int pagesize = getpagesize();
byte* entry = NULL;
// TODO: compute correct entry
if (mprotect(&p->array, p->size-1, PROT_READ))
{
exit(0);
}
if (mprotect(&p->array, p->size, PROT_WRITE))
{
exit(0);
}
entry = (void*)(p->array + index);
return entry;
}
I also have the following handler:
static void handler(int sig, siginfo_t *si, void* unused)
{
// TODO: Use fprintf or perror to print
// a message indicating a segmentation fault
// happened and provide the memory address
// where the fault happened
fprintf(stderr, "Segmentation Fault\n k = %d, %p\n", sig, si >si_addr);
}
Finally, the main method:
int main(void)
{
struct sigaction sa;
/*
* TODO: Overwrite the signal handler for
* SIGSEGV
*/
memset(&sa, '\0', sizeof(sa));
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
if (sigaction(SIGSEGV, &sa, NULL) == -1)
{
perror("sigaction");
exit(EXIT_FAILURE);
}
}
There's also several tests to run in the main method, but I've left those out because I encounter an error before I even reach them. What happens is, the handler prints forever (Segmentation fault, k = 11, 0x8). I do not know the significance of 11 or 0x8, but it does not stop printing that sequence until I force it.
Any help would be greatly appreciated, and I apologize for the length of this post. Thanks
Edit: from what I can see, the handler continues to print. It's not so much that I'm getting a seg fault (I might be), but whatever I put in the handler it continues to print. Also if I change it to perror it does the same. What can I do to allow the program to continue after the handler?
I am writing a char device that takes as input with ioctl a function pointer and a buffer pointer.
I want to modify the user machine context so that back in user mode, that function is executed with a new stack pointed by that buffer pointer.
What I have done is the following :
long ioctl_funcs(struct file *filp,unsigned int cmd, unsigned long arg)
{
int ret = 0;
switch(cmd) {
case IOCTL_SET_FUN:
printk(KERN_INFO "start\n");
struct myarg* a;
a = (struct myarg*) arg;
struct pt_regs* regs = task_pt_regs(current);
regs->ip = a->func;// func is a function implemented in user space
regs->sp = a->stack;// stack is the buffer allocated in user space with malloc
break;
}
return ret;
}
The good news is that the function is activated, the bad one is that the stack is the same (I have used gdb to test it).
In particular even if : regs->sp = 0; the new function is executed when it should crash since it should have no stack.
It seems the assignment of the stack pointer in this way is ineffective.
Why? How should I correctly assign the stack pointer?
The linux kernel version is : 3.18.106 and it is executed on Virtual Box.
Why does some glibc's APIs(such as function malloc(), realloc() or free()) can not be correctly called in threads that are created by syscall clone?
Here is my code only for testing:
int thread_func( void *arg )
{
void *ptr = malloc( 4096 );
printf( "tid=%d, ptr=%x\n", gettid(), ptr );
sleep(1);
if( ptr )
free( ptr );
return 0;
}
int main( int argc, char **argv )
{
int i, m;
void *stk;
int stksz = 1024 * 128;
int flag = CLONE_VM | CLONE _FILES | CLONE_FS | CLONE_SIGHAND;
for( i=m=0; i < 100; i++ )
{
stk = malloc( stksz );
if( !stk ) break;
if( clone( thread_func, stk+stksz, flags, NULL, NULL, NULL, NULL ) != -1 )
m++;
}
printf( "create %d thread\n", m );
sleep(10);
return 0;
}
Testing result: thread thread_func or main thread main will be blocked on malloc() or free() function randomly. Or sometimes causes malloc() or free() to crash.
I think may be malloc() and free() need certain TLS data to distinguish every thread.
Does anyone know the reason, and what solution can been used to resolve this problem?
I think may be malloc() and free() need certain TLS data to distinguish every thread.
Glibc's malloc() and free() do not rely on TLS. They use mutexes to protect the shared memory-allocation data structures. To reduce contention for those, they employ a strategy of maintaining separate memory-allocation arenas with independent metadata and mutexes. This is documented on their manual page.
After correcting the syntax errors in your code and dummying-out the call to non-existent function gettid() (see comments on the question), I was able to produce segmentation faults, but not blockage. Perhaps you confused the exit delay caused by your program's 10-second sleep with blockage.
In addition to any issue that may have been related to your undisclosed implementation of gettid(), your program contains two semantic errors, each producing undefined behavior:
As I already noted in comments, it passes the wrong child-stack pointer values.*
It uses the wrong printf() directive in thread_func() for printing the pointer. The directive for pointer values is %p; %x is for arguments of type unsigned int.
After I corrected those errors as well, the program consistently ran to completion for me. Revised code:
int thread_func(void *arg) {
void *ptr = malloc(4096);
// printf( "tid=%d, ptr=%x\n", gettid(), ptr );
printf("tid=%d, ptr=%p\n", 1, ptr);
sleep(1);
if (ptr) {
free(ptr);
}
return 0;
}
int main(int argc, char **argv) {
int i, m;
char *stk; // Note: char * instead of void * to afford arithmetic
int stksz = 1024 * 128;
int flags = CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND;
for (i = m = 0; i < 100; i++) {
stk = malloc( stksz );
if( !stk ) break;
if (clone(thread_func, stk + stksz - 1, flags, NULL, NULL, NULL, NULL ) != -1) {
m++;
}
}
printf("create %d thread\n", m);
sleep(10);
return 0;
}
Even with that, however, all is not completely well: I see various anomalies in the program output, especially near the beginning.
The bottom line is that, contrary to your assertion, you are not creating any threads, at least not in the sense that the C library recognizes. You are merely creating processes that have behavior similar to threads'. That may be sufficient for some purposes, but you cannot rely on the system to treat such processes identically to threads.
On Linux, bona fide threads that the system and standard library will recognize are POSIX threads, launched via pthread_create(). (I note here that modifying your program to use pthread_create() instead of clone() resolved the output anomalies for me.) You might be able to add flags and arguments to your clone() calls that make the resulting processes enough like the Linux implementation of pthreads to be effectively identical, but whyever would you do such a thing instead of just using real pthreads in the first place?
* The program also performs pointer arithmetic on a void *, which C does not permit. GCC accepts that as an extension, however, and since your code is deeply Linux-specific anyway, I'm letting that slide with only this note.
Correct, malloc and free need TLS for at least the following things:
The malloc arena attached to the current thread (used for allocation operations).
The errno TLS variable (written to when system calls fail).
The stack protector canary (if enabled and the architecture stores the canary in the TCB).
The malloc thread cache (enabled by default in the upcoming glibc 2.26 release).
All these items need a properly initialized thread control block (TCB), but curiously, until recently and as far as malloc/free was concerned, it almost did not matter if a thread created with clone was shared with another TCB (so that the data is no longer thread-local):
Threads basically never reattach themselves to a different arena, so the arena TLS variable is practically read-only after initialization—and multiple threads can share a single arena. errno can be shared as long as system calls only fail in one of the threads undergoing sharing. The stack protector canary is read-only after process startup, and its value is identical across threads anyway.
But all this is an implementation detail, and things change radically in glibc 2.26 with its malloc thread cache: The cache is read and written without synchronization, so it is very likely that what you are trying to do results in memory corruption.
This is not a material change in glibc 2.26, it is always how things were: calling any glibc function from a thread created with clone is undefined. As John Bollinger pointed out, this mostly worked by accident before, but I can assure you that it has always been completely undefined.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I'm trying to write a program that can obtain a 1GB memory from the system by malloc(1024*1024*1024).
After I got the start address of the memory, In my limited understanding, if I want to initialize it, just using memset() to achieve. But the truth is there will trigger a segfault after a while.
And I tried using gdb to find where cause it, finally found if I do some operate of memory more than 128 MB will lead to this fault.
Is there has any rule that limits program just can access memory less than 128 MB? Or I used the wrong way to allocate and initialize it?
If there is a need for additional information, please tell me.
Any suggestion will be appreciated.
[Platform]
Linux 4.10.1 with gcc 5.4.0
Build program with gcc test.c -o test
CPU: Intel i7-6700
RAM: 16GB
[Code]
size_t mem_size = 1024 * 1024 * 1024;
...
void *based = malloc(mem_size); //mem_size = 1024^3
int stage = 65536;
int initialized = 0;
if (based) {
printf("Allocated %zu Bytes from %lx to %lx\n", mem_size, based, based + mem_size);
} else {
printf("Error in allocation.\n");
return 1;
}
int n = 0;
while (initialized < mem_size) { //initialize it in batches
printf("%6d %lx-%lx\n", n++, based+initialized, based+initialized+stage);
memset(based + initialized, '$', stage);
initialized += stage;
}
[Result]
Allocated 1073741824 Bytes from 7f74c9e66010 to 7f76c9e66010
...
2045 7f7509ce6010-7f7509d66010
2046 7f7509d66010-7f7509de6010
2047 7f7509de6010-7f7509e66010
2048 7f7509e66010-7f7509ee6010 //2048*65536(B)=128(MB)
Segmentation fault (core dumped)
There are two possible issues here. The first is that you're not using malloc() correctly. You need to check if it returns NULL, or a non-NULL value.
The other issue could be that the OS is over-committing memory, and the out-of-memory (OOM) killer is terminating your process. You can disable over-committing of memory and getting dumps to detect via these instructions.
Edit
Two major problems:
Don't do operations with side effects (ie: n++) inside a logging statement. VERY BAD practice, as logging calls are often removed at compile time in large projects, and now the program behaves differently.
Cast based to a (char *).
This should help with your problem.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
size_t mem_size = 1024 * 1024 * 1024;
printf("MEMSIZE: %lu\n", mem_size);
printf("SIZE OF: void*:%lu\n", sizeof(void*));
printf("SIZE OF: char*:%lu\n", sizeof(char*));
void *based = malloc(mem_size); //mem_size = 1024^3
int stage = 65536;
int initialized = 0;
if (based) {
printf("Allocated %zu Bytes from %p to %p\n", mem_size, based, based + mem_size);
} else {
printf("Error in allocation.\n");
return 1;
}
int n = 0;
while (initialized < mem_size) { //initialize it in batches
//printf("%6d %p-%p\n", n, based+initialized, based+initialized+stage);
n++;
memset((char *)based + initialized, '$', stage);
initialized += stage;
}
free(based);
return 0;
}
Holy, I found the problem - pointer type goes wrong.
Here is the complete code
int main(int argc, char *argv[]) {
/*Allocate 1GB memory*/
size_t mem_size = 1024 * 1024 * 1024;
// the problem is here, I used to use pointer as long long type
char* based = malloc(mem_size);
// and it misleading system to calculate incorrect offset
if (based) {
printf("Allocated %zu Bytes from %lx to %lx\n", mem_size, based, based + mem_size);
} else {
printf("Allocation Error.\n");
return 1;
}
/*Initialize the memory in batches*/
size_t stage = 65536;
size_t initialized = 0;
while (initialized < mem_size) {
memset(based + initialized, '$', stage);
initialized += stage;
}
/*And then I set the breakpoint, check the memory content with gdb*/
...
return 0;
Thank you for the people who have given me advice or comments :)
It is very unusual for a process to need such a large chunk of continuous memory and yes, the kernel does impose such memory limitations. You should probably know that malloc() when dealing with a memory request larger than 128 Kb it calls mmap() behind the curtains. You should try to use that directly.
You should also know that the default policy for the kernel when allocating is to allocate more memory than it has.
The logic is that most allocated memory is not actually used so it relatively safe to allow allocations that exceed the actual memory of the system.
EDIT: As some people have it pointed out, when your process does start to use the memory allocated successfully by the kernel it will get killed by the OOM Killer. This code has produced the following output:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int arc, char**argv)
{
char *c = malloc(sizeof(char) * 1024 * 1024 * 1024 * 5);
if(c)
{
printf("allocated memory\n");
memset(c, 1, sizeof(char) * 1024 * 1024 * 1024 * 5);
free(c);
}
else
{
printf("Out of memory\n");
}
return 0;
}
Output:
$ ./a.out
allocated memory
Killed
But after you change the limits of the system:
# echo 2 > /proc/sys/vm/overcommit_memory
# exit
exit
$ ./a.out
Out of memory
As you can see, the memory allocation was successful on the system and the problem appeared only after the memory was used
:EDIT
There are limits that the kernel imposes on how much memory you can allocate and you can check them out with these commands:
grep -e MemTotal -e CommitLimit -e Committed_AS /proc/meminfo
ulimit -a
The first command will print the total memory and the second will display the limit that the kernel imposes on allocations (CommitLimit). The limit is based on your system memory and the over-commit ratio defined on your system that you can check with this command cat /proc/sys/vm/overcommit_ratio.
The Committed_AS is the memory that is already allocated to the system at the moment. You will notice that this can exceed the Total Memory without causing a crash.
You can change the default behavior of your kernel to never overcommit by writing echo 2 > /proc/sys/vm/overcommit_memory You can check the man pages for more info on this.
I recommend checking the limits on your system and then disabling the default overcommit behavior of the kernel. Then try to see if your system can actually allocated that much memory by checking to see if malloc() of mmap() fail when allocating.
sources: LSFMM: Improving the out-of-memory killer
and Mastering Embedded Linux Programming by Chris Simmonds