1

I am trying to implement simple user level thread library in c.when one thread start and this thread call second thread. this second thread run correctly but when it exit program crash.here is my coding.

//**********************************************
#include <setjmp.h>

typedef void *any_ptr;
/* Define Boolean type, and associated constants. */
typedef int Boolean;
typedef void (*ThreadFunc)(any_ptr);

#define TRUE ((Boolean)1);
#define FALSE ((Boolean)0);

typedef struct TheadSystem
{
  queue<any_ptr> readyQ;
  // currently executing thread
  jmp_buf lastContext; // location on which the system jumps after all threads have exited
  char name[30]; // optional name string of a thread, may be used for debugging
  jmp_buf context;   // saved context of this thread
  signal_t *sig; // signal that wakes up a waiting thread
  ThreadFunc func; // function that this thread started executing
  any_ptr    arg;
}TheadSystem;

void t_start(ThreadFunc f, any_ptr v, char *name);
void t_yield();
void block();
void unblock();
void t_sig(Condition cond, any_ptr val, Boolean queue_signal);
void t_fork(ThreadFunc f, any_ptr v, char *name);
void t_exit(int val);

My implementation of threads.h

#include "threads.h"
#include<iostream>
#include<queue>

using namespace std;
TheadSystem th;

queue<any_ptr> blocked_queue;
jmp_buf set_env,ready_env,yeild_buf;

void t_start(ThreadFunc f, any_ptr v, char *name){  
  if(!th.ready_queue.empty()){
    cout<<"sorry thread already started now you have to create by t_fork:"<<endl;
  }
  else{
    th.ready_queue.push(th.context);
      if(!setjmp(th.context)){
        memcpy(th.lastContext,th.context,sizeof(jmp_buf));
        th.arg=v;
        th.func=f;

        //memcpy(th.currentThread->context,set_env,sizeof(jmp_buf));
        //cout<<"when jmp buf set then:"<<endl;
        th.ready_queue.push(th.context);
        th.func(th.arg);
      }
    //cout<<"after come back from long jump:"<<endl;
  }
}

void t_yield(){
  jmp_buf *j=(jmp_buf *)th.ready_queue.front();
  th.ready_queue.front()=th.context;

  longjmp(*j,2);
}

void t_fork(ThreadFunc f, any_ptr v, char *name){
  memcpy(th.lastContext,th.context,sizeof(jmp_buf));
  if(!setjmp(th.context)){
    f(v);
    th.ready_queue.push(th.context);
  }else
  {
  }
}//end of t_fork

void t_exit(int val){   
  cout<<"before long jump in t_exit"<<endl;
  jmp_buf *j=(jmp_buf *)th.ready_queue.front();
  th.ready_queue.pop();
  longjmp(*j,2);    
}

void block(){
  blocked_queue.push(th.context);
  jmp_buf *j=(jmp_buf *)th.ready_queue.front();
  th.ready_queue.pop();
  longjmp(*j,2);    
}

void unblock(){
  th.ready_queue.push(th.context);
  jmp_buf *j=(jmp_buf *)blocked_queue.front();
  blocked_queue.pop();
  longjmp(*j,2);    
}

my test case is

#include<iostream>
#include<setjmp.h>
#include<stdio.h>
#include "threads.h"
#include<queue>

using namespace std;    
void fun2(any_ptr v){
  cout<<"in 2nd function:"<<endl;
  t_exit(0);
}

void helloworld(any_ptr v){
  cout<<"in hello world from start"<<endl;
  t_fork(fun2,NULL,"no value");
  cout<<"after second thread:"<<endl;
  cout<<"before exit"<<endl;
  t_exit(0);
}

void main(){
  cout<<"1 start"<<endl;
  t_start(helloworld, NULL, "my function");
  cout<<"main function"<<endl;
}//end of void main
Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
yasiriqbal776
  • 406
  • 3
  • 15
  • Welcome to StackOverflow. So you expect that the SO community debugs your program? What have you've done so far? – harper Apr 29 '14 at 06:36
  • 6
    The use of `setjmp`/`longjmp` in C++ can cause resource leaks, and may even be dangerous. If you `longjmp` the stack wont be unwound in the way needed for C++, causing objects to be lost and their destructors not being called. Also, at the destination of the jump the objects may not be properly initialized (because they may have already been destructed). In short, be ***very*** careful when using it in a C++ program, or better yet don't use it at all! – Some programmer dude Apr 29 '14 at 06:36
  • 1
    Where is `t_exit` called? – M.M Apr 29 '14 at 06:40
  • 1
    What is `ThreadFunc` ? – M.M Apr 29 '14 at 06:41
  • in t_exit if i call longjump ot last context then it does not crash and program goes directly to end. i dry run it several time but here pointer Ahhhhhh.... :( – yasiriqbal776 Apr 29 '14 at 06:43
  • With c++ you may make your life easier using Boost.context: http://www.boost.org/doc/libs/1_55_0/libs/context/doc/html/index.html – oakad Apr 29 '14 at 06:44
  • int main() { t_start(firstThread,NULL,"firstThread"); } void firstThread(any_ptr ptr) { printf("Hello world: I am thread#1\n"); t_fork(Thread2,NULL,"2ndThread"); printf("First thread calling exit\n"); t_exit(0); } – yasiriqbal776 Apr 29 '14 at 06:46
  • Matt McNabb : t_exit is called in thread function – yasiriqbal776 Apr 29 '14 at 06:47
  • threadfunction is void function which get reference to any function generically – yasiriqbal776 Apr 29 '14 at 06:48
  • So, basically, you are writing your own threading system, using `setjmp/longjmp`? What is the crash? – Mats Petersson Apr 29 '14 at 06:57
  • Using `jmp_buf *j` after you've invalidated it with `th.ready_queue.pop();` is unlikely to end well (even if you somehow manage to use `setjmp` without causing undefined behaviour). – Mike Seymour Apr 29 '14 at 07:01
  • It would REALLY help if you posted a complete program that can be compiled. People who are commenting clearly don't understand your code (and I can't say I'm certain of it either) – Mats Petersson Apr 29 '14 at 07:05
  • in t_exit when i call longjmp then control go to fork.(because when one thread start it come to t_start() and second thread is created inside first thread so second run correctly and when it exit it come again to t_fork because of longjmp. here controll have to come where t_fork was called in first thread. but here it crash. ithink in stack he didnt find the address of that function – yasiriqbal776 Apr 29 '14 at 07:07
  • To me, it looks like you are (implictly) casting objects into pointers to objects - but without complete code, it's hard to say exactly what you are doing, what `front` and `push` does, etc. – Mats Petersson Apr 29 '14 at 07:08
  • i have updated my code to complete code – yasiriqbal776 Apr 29 '14 at 07:20
  • The code you have posted does not compile at all, as you have a few typos. Even after fixing typos, it doesn't compile cleanly (without warnings with g++ 4.8.2 on Linux x86-64- it may well work with some other compiler on some other OS). – Mats Petersson Apr 29 '14 at 07:42
  • i compile this on microsoft visual studio 2008 in windows 8 – yasiriqbal776 Apr 29 '14 at 07:52
  • and by just removing t_signal function only other program is compilabe – yasiriqbal776 Apr 29 '14 at 07:53

3 Answers3

2

Here is one problem:

In the t_start function you do this:

th.ready_queue.push(th.context);

The ready_queue is a queue of pointers, but th.context is not a pointer.

Then in the t_yield function you do

jmp_buf *j=(jmp_buf *)th.ready_queue.front();

So you push non-pointer object, and pop them as pointers. If you try to access a non-pointer object as a pointer you have undefined behavior.

You code, if it compiles without errors, should at least give you a lot of warnings, and if you only get a few warnings then I suggest you enable more warnings. When the compiler gives you a warning, it's often a sign about you doing something you should not be doing, like doing something that leads just to undefined behavior. Just silencing the warnings by e.g. type-casting is a very bad solution as it doesn't actually solve the cause of the warning.

Also, using void* is a very good sign of bad code coming. Don't use it if you can avoid it, and in this case it's really not needed in most of the places you use it (like the ready_queue).

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • but if i am maintaining wrong queue. then why it work correctly if i create only one thread. when one thread created it come to t_start and after then it goes to function like in above example helloworld() and after that it come to t_exit(), from here it comes to t_start because of longjmp. and then come back to main – yasiriqbal776 Apr 29 '14 at 08:07
  • issue is here when i create another thread and when second thread exit and it come in function by longjmp then it shows undefined behaviour – yasiriqbal776 Apr 29 '14 at 08:09
  • 2
    @Joachim - th.context is an array, so behaves like a pointer. The problem here is that yasiriqbal776 pushes the address of th.context, then overwrote the buffer -- so wedon't actually restore the original context. – Michael J Apr 29 '14 at 08:11
1

There are SEVERAL problems with this code, some of which Joachim Pileborg points out.

Another problem is that you only have one context, which you are using multiple times to store different data, yet expect the data to be there when you come back.

The solution is to split your ThreadSystem and your Thread (the actual context of a thread) into separate objects:

struct Thread
{
    jmp_buf context;   // saved context of this thread
    void*    arg;
    ThreadFunc func; // function that this thread started executing
};

After removing stuff that isn't currently used, the ThreadSystem looks like this:

struct ThreadSystem
{
    queue<Thread*> ready_queue;
};

The thread creation/exit functions now look like this:

void t_start(ThreadFunc f, void* v)
{    
    if(!sys.ready_queue.empty()){
        cout<<"sorry thread already started now you have to create by t_fork:"<<endl;
    }
    else{
    Thread* th = new Thread;
        sys.ready_queue.push(th);


        if(!setjmp(th->context)){
            th->arg=v;
            th->func=f;

        cout << "&th->context=" << &th->context << endl;
            th->func(th->arg);
        }
    }
}

void t_fork(ThreadFunc f, void* v){
    Thread* th = new Thread; 
    th->func = f;
    th->arg = v;
    if(!setjmp(th->context))
    {
    cout << "&th->context=" << &th->context << endl;
        f(v);
        sys.ready_queue.push(th);
    }
}//end of t_fork

void t_exit(int val){   
    cout<<"before long jump in t_exit"<<endl;
    Thread* th=sys.ready_queue.front();
    sys.ready_queue.pop();
    // Memory leak here. We can't delete `th`, and still have a context.
    longjmp(th->context,2);  
}

But as you can see, there is a problem in destroying the thread - so some other solution would have to be found for this. I'm not sure this is a great solution, but this works (to the limited degree of executing the test-code posted), where the original code didn't.

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
0

OK. My first pass at this was inadequate as I didn't spend sufficient time understanding the original code.

The code is buggy and messy, but probably fixable. When you push th.context onto the ready_queue you need to save the whole buffer, not just the buffer address. Probably many other problems.

Update 1

Solved first problem by wrapping the jmp_buf in a struct declaration and then making ready_queue and blocked_queue queues of structs. Then a simple assign will transfer the buffer contents.

struct SJBuff
{
    jmp_buf jb;
};

Second problem: in t_start(), don't push th.context before it is first initialised.

else
{
    // remove this line
    // th.readyQ.push(th.context);

    if(!setjmp(th.context.jb))
    {

End Update 1

Notwithstanding that, I really cannot recommend using setjmp(). Modern architectures have moved on and just saving a few registers does not really capture enough state. I shudder to think what an optimizing compiler might do to your code. Piplining, conditional execution, lazy evaluation, extra registers, unscheduled system interrupts, ...

If you focus on your real objectives, there is probably a better way to do it.

Michael J
  • 7,631
  • 2
  • 24
  • 30
  • I don't actually think this is the answer (or the problem). The code is messy, using various casts, we don't know how the `queue` works, and various other things, which is at least as likely to cause the problem. – Mats Petersson Apr 29 '14 at 06:59
  • The OP is implementing user-level context switching (a.k.a. green threads), not exception handling. – Hristo Iliev Apr 29 '14 at 07:10
  • The C++ standard explicitly says of setjmp/longjmp that it's only valid in a situation where `setjmp` could be replaced by `catch`, and `longjmp` replaced by `throw`. See [support.runtime] – Matt McNabb 1 min ago edit – M.M Apr 29 '14 at 08:37