[BusyBox] Implement fork using longjmp [PATCH]

Rob Landley rob at landley.net
Sat Mar 5 20:03:46 UTC 2005


On Friday 04 March 2005 12:27 pm, Shaun Jackman wrote:
> On Thu, 3 Mar 2005 21:31:00 -0500, Rob Landley <rob at landley.net> wrote:
> > It says "macro" yet it doesn't actually use macros.  If you simply
> > declare jmp_buf on the stack, you don't have to worry about an array of
> > global instances...
> >
> > Rob
>
> fork, exit and waitpid are implemented using macros that call helper
> functions.

The helper functions were what I noticed.

> Putting the jmp_buf on the stack is syntactically tricky since fork is
> typically used in an expression like so:
> 	if( (pid = fork()) == 0 ) {

Youre implementation:

+jmp_buf* bb_fork(void)
+{
+	if( child_pid > 0 )
+		bb_error_msg_and_die("Too many forks.");
+	return &fork_jmp_buf[child_pid++];
+}

A version of that which should work entirely as a macro:

#define bb_fork() (child_pid>0 ? bb_error_msg_and_die("Too many forks.") \
						: &fork_jmp_buf[child_pid++])

I'd also like to point out that 2/3 of what's that doing is limitation 
checking for a limitation the implementation shouldn't have...

Also, inline functions work too.  Yeah, I know you're saying "an inline in 
busybox???" but if it saves you a stack frame and lets the compiler's 
optimizer get a better grip on the code, it's not always a loss for really 
_small_ functions.  (Worth experimenting, anyway.  It's quite possible out of 
line would be smaller, but not guaranteed.)

> The jmp_buf could be put on the stack using alloca...
> 	#define fork() setjmp(*(jmp_buf*)alloca(sizeof(jmp_buf)))
> ... or using GCC's Declarations in Expressions feature [1]
> 	#define fork() ({ jmp_buf fork_buf; setjmp(fork_buf); })
> ... but these both use somewhat esoteric features of the language and
> implementation. Once the jmp_buf is stored on the stack, it is still
> necessary to store a global reference to it so that the exit macro
> knows where to longjmp. If you have a helper function maintaining a
> list of jmp_buf buffers pointers anyways, it may as well allocate the
> memory for them as well.

Okay, exit needing a global to find fork is a decent argument, so you want a 
linked list then.  Exit should only care about two globals: the return to 
last fork pointer and the exit value int.  The return to last fork pointer 
can be to a structure containing the jmp_buf and the pointer to the previous 
malloced fork structure.

Possibly something along the lines of (untested and off the top of my head):

struct bb_jmp_buf_list {
  jmp_buf jb;
  bb_jmp_buf_list *next;
} *fork_jmp_buf=NULL;

static inline bb_fork(void)
{
	struct bb_jmp_buf_list *tmp=(struct_bb_jmp_buf_list *)
		xmalloc(sizeof(struct bb_jmp_buf_list));
	tmp->next=fork_jmp_buf;
	fork_jmp_buf=tmp;
	return setjmp(tmp->jb);
}

static inline bb_exit(int retval)
{
	jmp_buf jb=fork_jmp_buf->walrus;;
	struct bb_jmp_buf_list *tmp=fork_jmp_buf;

	if(!fork_jmp_buf) do_real_exit(retval); // Need to provide this! 
	fork_jmp_buf=fork_jmp_buf->next;
	free(tmp);
	exit_status=retval;
	longjmp(jb,1);	// Run parent code
}

static inline bb_waitpid(pid_t pid, int *status, int options)
{
	return exit_status;
}

I know your waitpid was more complicated, which would be easy enough to 
replicate, but what possible use is ECHILD in an environment where fork 
blocks?  I'm confused...

And of course if xmalloc was instead alloca(), exit wouldn't need to jump 
through hoops to free jmp_buf and then use it afterwards (albeit at the cost 
of changing the semantics from fork to vfork).  It's almost certainly not a 
gain to inline it as is, but the alloca version probably would be...

Rob



More information about the busybox mailing list