diff options
author | rsc <devnull@localhost> | 2003-09-30 17:47:42 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2003-09-30 17:47:42 +0000 |
commit | 76193d7cb0457807b2f0b95f909ab5de19480cd7 (patch) | |
tree | 97e538c7e38181431e90289a0fe8b6b7ce1f8f3c /src/libthread | |
parent | ed7c8e8d02c02bdbff1e88a6d8d1419f39af48ad (diff) | |
download | plan9port-76193d7cb0457807b2f0b95f909ab5de19480cd7.tar.gz plan9port-76193d7cb0457807b2f0b95f909ab5de19480cd7.tar.bz2 plan9port-76193d7cb0457807b2f0b95f909ab5de19480cd7.zip |
Initial revision
Diffstat (limited to 'src/libthread')
57 files changed, 4091 insertions, 0 deletions
diff --git a/src/libthread/386.c b/src/libthread/386.c new file mode 100644 index 00000000..2f391bff --- /dev/null +++ b/src/libthread/386.c @@ -0,0 +1,21 @@ +#include "threadimpl.h" + +static void +launcher386(void (*f)(void *arg), void *arg) +{ + (*f)(arg); + threadexits(nil); +} + +void +_threadinitstack(Thread *t, void (*f)(void*), void *arg) +{ + ulong *tos; + + tos = (ulong*)&t->stk[t->stksize&~7]; + *--tos = (ulong)arg; + *--tos = (ulong)f; + t->sched.pc = (ulong)launcher386; + t->sched.sp = (ulong)tos - 8; /* old PC and new PC */ +} + diff --git a/src/libthread/FreeBSD-386.s b/src/libthread/FreeBSD-386.s new file mode 100644 index 00000000..624518e0 --- /dev/null +++ b/src/libthread/FreeBSD-386.s @@ -0,0 +1,18 @@ + +.globl _xinc +_xinc: + movl 4(%esp), %eax + lock incl 0(%eax) + ret + +.globl _xdec +_xdec: + movl 4(%esp), %eax + lock decl 0(%eax) + jz iszero + movl %eax, 1 + ret +iszero: + movl %eax, 0 + ret + diff --git a/src/libthread/LICENSE b/src/libthread/LICENSE new file mode 100644 index 00000000..a5d7d87d --- /dev/null +++ b/src/libthread/LICENSE @@ -0,0 +1,258 @@ +The Plan 9 software is provided under the terms of the +Lucent Public License, Version 1.02, reproduced below, +with the following exceptions: + +1. No right is granted to create derivative works of or + to redistribute (other than with the Plan 9 Operating System) + the screen imprinter fonts identified in subdirectory + /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida + Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans + Typewriter83), identified in subdirectory /sys/lib/postscript/font. + These directories contain material copyrights by B&H Inc. and Y&Y Inc. + +2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font + are subject to the GNU GPL, reproduced in the file /LICENSE.gpl. + +3. The ghostscript program in the subdirectory /sys/src/cmd/gs is + covered by the Aladdin Free Public License, reproduced in the file + /LICENSE.afpl. + +=================================================================== + +Lucent Public License Version 1.02 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE +PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original + Program, and + b. in the case of each Contributor, + + i. changes to the Program, and + ii. additions to the Program; + + where such changes and/or additions to the Program were added to the + Program by such Contributor itself or anyone acting on such + Contributor's behalf, and the Contributor explicitly consents, in + accordance with Section 3C, to characterization of the changes and/or + additions as Contributions. + +"Contributor" means LUCENT and any other entity that has Contributed a +Contribution to the Program. + +"Distributor" means a Recipient that distributes the Program, +modifications to the Program, or any part thereof. + +"Licensed Patents" mean patent claims licensable by a Contributor +which are necessarily infringed by the use or sale of its Contribution +alone or when combined with the Program. + +"Original Program" means the original version of the software +accompanying this Agreement as released by LUCENT, including source +code, object code and documentation, if any. + +"Program" means the Original Program and Contributions or any part +thereof + +"Recipient" means anyone who receives the Program under this +Agreement, including all Contributors. + +2. GRANT OF RIGHTS + + a. Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare derivative works of, publicly display, + publicly perform, distribute and sublicense the Contribution of such + Contributor, if any, and such derivative works, in source code and + object code form. + + b. Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, if + any, in source code and object code form. The patent license granted + by a Contributor shall also apply to the combination of the + Contribution of that Contributor and the Program if, at the time the + Contribution is added by the Contributor, such addition of the + Contribution causes such combination to be covered by the Licensed + Patents. The patent license granted by a Contributor shall not apply + to (i) any other combinations which include the Contribution, nor to + (ii) Contributions of other Contributors. No hardware per se is + licensed hereunder. + + c. Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. Each + Contributor disclaims any liability to Recipient for claims brought by + any other entity based on infringement of intellectual property rights + or otherwise. As a condition to exercising the rights and licenses + granted hereunder, each Recipient hereby assumes sole responsibility + to secure any other intellectual property rights needed, if any. For + example, if a third party patent license is required to allow + Recipient to distribute the Program, it is Recipient's responsibility + to acquire that license before distributing the Program. + + d. Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A. Distributor may choose to distribute the Program in any form under +this Agreement or under its own license agreement, provided that: + + a. it complies with the terms and conditions of this Agreement; + + b. if the Program is distributed in source code or other tangible + form, a copy of this Agreement or Distributor's own license agreement + is included with each copy of the Program; and + + c. if distributed under Distributor's own license agreement, such + license agreement: + + i. effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii. effectively excludes on behalf of all Contributors all liability + for damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; and + iii. states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party. + +B. Each Distributor must include the following in a conspicuous + location in the Program: + + Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights + Reserved. + +C. In addition, each Contributor must identify itself as the +originator of its Contribution in a manner that reasonably allows +subsequent Recipients to identify the originator of the Contribution. +Also, each Contributor must agree that the additions and/or changes +are intended to be a Contribution. Once a Contribution is contributed, +it may not thereafter be revoked. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain +responsibilities with respect to end users, business partners and the +like. While this license is intended to facilitate the commercial use +of the Program, the Distributor who includes the Program in a +commercial product offering should do so in a manner which does not +create potential liability for Contributors. Therefore, if a +Distributor includes the Program in a commercial product offering, +such Distributor ("Commercial Distributor") hereby agrees to defend +and indemnify every Contributor ("Indemnified Contributor") against +any losses, damages and costs (collectively"Losses") arising from +claims, lawsuits and other legal actions brought by a third party +against the Indemnified Contributor to the extent caused by the acts +or omissions of such Commercial Distributor in connection with its +distribution of the Program in a commercial product offering. The +obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. +In order to qualify, an Indemnified Contributor must: a) promptly +notify the Commercial Distributor in writing of such claim, and b) +allow the Commercial Distributor to control, and cooperate with the +Commercial Distributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such +claim at its own expense. + +For example, a Distributor might include the Program in a commercial +product offering, Product X. That Distributor is then a Commercial +Distributor. If that Commercial Distributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Distributor's responsibility +alone. Under this section, the Commercial Distributor would have to +defend claims against the Contributors related to those performance +claims and warranties, and if a court requires any Contributor to pay +any damages as a result, the Commercial Distributor must pay those +damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS +PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY +WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY +OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely +responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement, including but not limited to +the risks and costs of program errors, compliance with applicable +laws, damage to or loss of data, programs or equipment, and +unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR +ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING +WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR +DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED +HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. EXPORT CONTROL + +Recipient agrees that Recipient alone is responsible for compliance +with the United States export administration regulations (and the +export control laws and regulation of any other countries). + +8. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against a Contributor with +respect to a patent applicable to software (including a cross-claim or +counterclaim in a lawsuit), then any patent licenses granted by that +Contributor to such Recipient under this Agreement shall terminate as +of the date such litigation is filed. In addition, if Recipient +institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program +itself (excluding combinations of the Program with other software or +hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and +survive. + +LUCENT may publish new versions (including revisions) of this +Agreement from time to time. Each new version of the Agreement will be +given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new +version of the Agreement is published, Contributor may elect to +distribute the Program (including its Contributions) under the new +version. No one other than LUCENT has the right to modify this +Agreement. Except as expressly stated in Sections 2(a) and 2(b) above, +Recipient receives no rights or licenses to the intellectual property +of any Contributor under this Agreement, whether expressly, by +implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and +the intellectual property laws of the United States of America. No +party to this Agreement will bring a legal action under this Agreement +more than one year after the cause of action arose. Each party waives +its rights to a jury trial in any resulting litigation. + diff --git a/src/libthread/Make.Darwin-PowerMacintosh b/src/libthread/Make.Darwin-PowerMacintosh new file mode 100644 index 00000000..14b8d4e7 --- /dev/null +++ b/src/libthread/Make.Darwin-PowerMacintosh @@ -0,0 +1,6 @@ +CC=gcc +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I${PREFIX}/include +O=o +AR=ar +ARFLAGS=rvc +NAN=nan64.$O diff --git a/src/libthread/Make.FreeBSD-386 b/src/libthread/Make.FreeBSD-386 new file mode 100644 index 00000000..087ed3ab --- /dev/null +++ b/src/libthread/Make.FreeBSD-386 @@ -0,0 +1,7 @@ +CC=gcc +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include +O=o +AR=ar +ARFLAGS=rvc +NAN=nan64.$O # default, can be overriden by Make.$(SYSNAME) +NAN=nan64.$O diff --git a/src/libthread/Make.HP-UX-9000 b/src/libthread/Make.HP-UX-9000 new file mode 100644 index 00000000..edbdc111 --- /dev/null +++ b/src/libthread/Make.HP-UX-9000 @@ -0,0 +1,6 @@ +CC=cc +CFLAGS=-O -c -Ae -I. +O=o +AR=ar +ARFLAGS=rvc +NAN=nan64.$O diff --git a/src/libthread/Make.Linux-386 b/src/libthread/Make.Linux-386 new file mode 100644 index 00000000..74b0252c --- /dev/null +++ b/src/libthread/Make.Linux-386 @@ -0,0 +1,7 @@ +CC=gcc +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. +O=o +AR=ar +ARFLAGS=rvc +NAN=nan64.$O # default, can be overriden by Make.$(SYSNAME) +NAN=nan64.$O diff --git a/src/libthread/Make.NetBSD-386 b/src/libthread/Make.NetBSD-386 new file mode 100644 index 00000000..087ed3ab --- /dev/null +++ b/src/libthread/Make.NetBSD-386 @@ -0,0 +1,7 @@ +CC=gcc +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include +O=o +AR=ar +ARFLAGS=rvc +NAN=nan64.$O # default, can be overriden by Make.$(SYSNAME) +NAN=nan64.$O diff --git a/src/libthread/Make.OSF1-alpha b/src/libthread/Make.OSF1-alpha new file mode 100644 index 00000000..3d45279b --- /dev/null +++ b/src/libthread/Make.OSF1-alpha @@ -0,0 +1,6 @@ +CC=cc +CFLAGS+=-g -c -I. +O=o +AR=ar +ARFLAGS=rvc +NAN=nan64.$O diff --git a/src/libthread/Make.SunOS-sun4u b/src/libthread/Make.SunOS-sun4u new file mode 100644 index 00000000..c5fe67b8 --- /dev/null +++ b/src/libthread/Make.SunOS-sun4u @@ -0,0 +1,2 @@ +include Make.SunOS-sun4u-$(CC) +NAN=nan64.$O diff --git a/src/libthread/Make.SunOS-sun4u-cc b/src/libthread/Make.SunOS-sun4u-cc new file mode 100644 index 00000000..829301de --- /dev/null +++ b/src/libthread/Make.SunOS-sun4u-cc @@ -0,0 +1,6 @@ +CC=cc +CFLAGS+=-g -c -I. -O +O=o +AR=ar +ARFLAGS=rvc +NAN=nan64.$O diff --git a/src/libthread/Make.SunOS-sun4u-gcc b/src/libthread/Make.SunOS-sun4u-gcc new file mode 100644 index 00000000..5c415948 --- /dev/null +++ b/src/libthread/Make.SunOS-sun4u-gcc @@ -0,0 +1,6 @@ +CC=gcc +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c +O=o +AR=ar +ARFLAGS=rvc +NAN=nan64.$O diff --git a/src/libthread/Makefile b/src/libthread/Makefile new file mode 100644 index 00000000..ebccc1ac --- /dev/null +++ b/src/libthread/Makefile @@ -0,0 +1,125 @@ + +# this works in gnu make +SYSNAME:=${shell uname} +OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'} + +# this works in bsd make +SYSNAME!=uname +OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g' + +# the gnu rules will mess up bsd but not vice versa, +# hence the gnu rules come first. + +include Make.$(SYSNAME)-$(OBJTYPE) + +PREFIX=/usr/local + +NUKEFILES= + +TGZFILES= + +LIB=libthread.a +VERSION=2.0 +PORTPLACE=devel/libthread +NAME=libthread + +OFILES=\ + $(OBJTYPE).$O\ + asm-$(SYSNAME)-$(OBJTYPE).$O\ + channel.$O\ + chanprint.$O\ + create.$O\ + debug.$O\ + exec-unix.$O\ + exit.$O\ + getpid.$O\ + id.$O\ + iocall.$O\ + ioclose.$O\ + ioopen.$O\ + ioproc.$O\ + ioread.$O\ + ioreadn.$O\ + iowrite.$O\ + kill.$O\ + lib.$O\ + main.$O\ + memset.$O\ + memsetd.$O\ + note.$O\ + proctab.$O\ + ref.$O\ + rendez.$O\ + sched.$O\ + +HFILES=\ + thread.h\ + label.h\ + threadimpl.h\ + +all: $(LIB) + +install: $(LIB) + test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3 + install -m 0644 thread.3 $(PREFIX)/man/man3/thread.3 + install -m 0644 ioproc.3 $(PREFIX)/man/man3/ioproc.3 + install -m 0644 thread.h $(PREFIX)/include/thread.h + install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB) + +tprimes: $(LIB) tprimes.$O + $(CC) -o tprimes tprimes.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf + +texec: $(LIB) texec.$O + $(CC) -o texec texec.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf + +$(LIB): $(OFILES) + $(AR) $(ARFLAGS) $(LIB) $(OFILES) + +NUKEFILES+=$(LIB) +.c.$O: + $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c + +%.$O: %.c + $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c + + +$(OFILES): $(HFILES) + +tgz: + rm -rf $(NAME)-$(VERSION) + mkdir $(NAME)-$(VERSION) + cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION) + tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz + rm -rf $(NAME)-$(VERSION) + +clean: + rm -f $(OFILES) $(LIB) + +nuke: + rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES) + +rpm: + make tgz + cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES + rpm -ba rpm.spec + cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm . + cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm . + scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software + +PORTDIR=/usr/ports/$(PORTPLACE) + +ports: + make tgz + rm -rf $(PORTDIR) + mkdir $(PORTDIR) + cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles + cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}') + (cd $(PORTDIR); make makesum) + (cd $(PORTDIR); make) + (cd $(PORTDIR); /usr/local/bin/portlint) + rm -rf $(PORTDIR)/work + shar `find $(PORTDIR)` > ports.shar + (cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz + scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software + +.phony: all clean nuke install tgz rpm ports diff --git a/src/libthread/Makefile.MID b/src/libthread/Makefile.MID new file mode 100644 index 00000000..05c34167 --- /dev/null +++ b/src/libthread/Makefile.MID @@ -0,0 +1,54 @@ +LIB=libthread.a +VERSION=2.0 +PORTPLACE=devel/libthread +NAME=libthread + +OFILES=\ + $(OBJTYPE).$O\ + asm-$(SYSNAME)-$(OBJTYPE).$O\ + channel.$O\ + chanprint.$O\ + create.$O\ + debug.$O\ + exec-unix.$O\ + exit.$O\ + getpid.$O\ + id.$O\ + iocall.$O\ + ioclose.$O\ + ioopen.$O\ + ioproc.$O\ + ioread.$O\ + ioreadn.$O\ + iowrite.$O\ + kill.$O\ + lib.$O\ + main.$O\ + memset.$O\ + memsetd.$O\ + note.$O\ + proctab.$O\ + ref.$O\ + rendez.$O\ + sched.$O\ + +HFILES=\ + thread.h\ + label.h\ + threadimpl.h\ + +all: $(LIB) + +install: $(LIB) + test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3 + install -m 0644 thread.3 $(PREFIX)/man/man3/thread.3 + install -m 0644 ioproc.3 $(PREFIX)/man/man3/ioproc.3 + install -m 0644 thread.h $(PREFIX)/include/thread.h + install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB) + +tprimes: $(LIB) tprimes.$O + $(CC) -o tprimes tprimes.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf + +texec: $(LIB) texec.$O + $(CC) -o texec texec.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf + diff --git a/src/libthread/NOTICE b/src/libthread/NOTICE new file mode 100644 index 00000000..8bf69d6a --- /dev/null +++ b/src/libthread/NOTICE @@ -0,0 +1,19 @@ +/* + * The authors of this software are Russ Cox, Sape Mullender, and Rob Pike. + * Copyright (c) 2003 by Lucent Technologies. + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. +*/ + +This is a Unix port of the Plan 9 thread library. + +Please send comments about the packaging +to Russ Cox <rsc@post.harvard.edu>. + diff --git a/src/libthread/README b/src/libthread/README new file mode 100644 index 00000000..8bf69d6a --- /dev/null +++ b/src/libthread/README @@ -0,0 +1,19 @@ +/* + * The authors of this software are Russ Cox, Sape Mullender, and Rob Pike. + * Copyright (c) 2003 by Lucent Technologies. + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. +*/ + +This is a Unix port of the Plan 9 thread library. + +Please send comments about the packaging +to Russ Cox <rsc@post.harvard.edu>. + diff --git a/src/libthread/asm-FreeBSD-386.s b/src/libthread/asm-FreeBSD-386.s new file mode 100644 index 00000000..7cad85cc --- /dev/null +++ b/src/libthread/asm-FreeBSD-386.s @@ -0,0 +1,49 @@ +.globl _setlabel +.type _setlabel,@function + +_setlabel: + movl 4(%esp), %eax + movl 0(%esp), %edx + movl %edx, 0(%eax) + movl %ebx, 4(%eax) + movl %esp, 8(%eax) + movl %ebp, 12(%eax) + movl %esi, 16(%eax) + movl %edi, 20(%eax) + xorl %eax, %eax + ret + +.globl _gotolabel +.type _gotolabel,@function + +_gotolabel: + movl 4(%esp), %edx + movl 0(%edx), %ecx + movl 4(%edx), %ebx + movl 8(%edx), %esp + movl 12(%edx), %ebp + movl 16(%edx), %esi + movl 20(%edx), %edi + xorl %eax, %eax + incl %eax + movl %ecx, 0(%esp) + ret + + +.globl _xinc +_xinc: + movl 4(%esp), %eax + lock incl 0(%eax) + ret + +.globl _xdec +_xdec: + movl 4(%esp), %eax + lock decl 0(%eax) + jz iszero + movl %eax, 1 + ret +iszero: + movl %eax, 0 + ret + diff --git a/src/libthread/asm-Linux-386.s b/src/libthread/asm-Linux-386.s new file mode 100644 index 00000000..75d965bf --- /dev/null +++ b/src/libthread/asm-Linux-386.s @@ -0,0 +1 @@ +.include "asm-FreeBSD-386.s" diff --git a/src/libthread/bundle.ports b/src/libthread/bundle.ports new file mode 100644 index 00000000..adfeb904 --- /dev/null +++ b/src/libthread/bundle.ports @@ -0,0 +1,42 @@ +--- Makefile --- +# New ports collection makefile for: libthread +# Date Created: 11 Feb 2003 +# Whom: rsc +# + +PORTNAME= libthread +PORTVERSION= 1.0 +CATEGORIES= devel +MASTER_SITES= http://pdos.lcs.mit.edu/~rsc/software/ +DISTNAME= libthread +EXTRACT_SUFX= .tgz + +MAINTAINER= rsc@post.harvard.edu + +MAN3= print.3 fmtinstall.3 +MLINKS= XXX +USE_REINPLACE= XXX (wkj says yes) + +.include <bsd.port.pre.mk> + +post-patch: + ${REINPLACE_CMD} -e 's,@@LOCAL@@,${PREFIX},g' ${WRKSRC}/Makefile + +.include <bsd.port.post.mk> + +--- pkg-comment --- +Plan 9 thread library +--- pkg-descr --- +Libthread is a port of Plan 9's thread library. + +WWW: http://pdos.lcs.mit.edu/~rsc/software/ +WWW: http://plan9.bell-labs.com/magic/man2html/2/thread + +Russ Cox +rsc@post.harvard.edu +--- pkg-plist --- +lib/libthread.a +include/thread.h +--- /dev/null --- +This is just a way to make sure blank lines don't +creep into pkg-plist. diff --git a/src/libthread/channel.c b/src/libthread/channel.c new file mode 100644 index 00000000..384f23fd --- /dev/null +++ b/src/libthread/channel.c @@ -0,0 +1,485 @@ +#include "threadimpl.h" + +static Lock chanlock; /* central channel access lock */ + +static void enqueue(Alt*, Channel**); +static void dequeue(Alt*); +static int altexec(Alt*, int); + +int _threadhighnentry; +int _threadnalt; + +static int +canexec(Alt *a) +{ + int i, otherop; + Channel *c; + + c = a->c; + /* are there senders or receivers blocked? */ + otherop = (CHANSND+CHANRCV) - a->op; + for(i=0; i<c->nentry; i++) + if(c->qentry[i] && c->qentry[i]->op==otherop && *c->qentry[i]->tag==nil){ + _threaddebug(DBGCHAN, "can rendez alt %p chan %p", a, c); + return 1; + } + + /* is there room in the channel? */ + if((a->op==CHANSND && c->n < c->s) + || (a->op==CHANRCV && c->n > 0)){ + _threaddebug(DBGCHAN, "can buffer alt %p chan %p", a, c); + return 1; + } + + return 0; +} + +static void +_chanfree(Channel *c) +{ + int i, inuse; + + inuse = 0; + for(i = 0; i < c->nentry; i++) + if(c->qentry[i]) + inuse = 1; + if(inuse) + c->freed = 1; + else{ + if(c->qentry) + free(c->qentry); + free(c); + } +} + +void +chanfree(Channel *c) +{ + lock(&chanlock); + _chanfree(c); + unlock(&chanlock); +} + +int +chaninit(Channel *c, int elemsize, int elemcnt) +{ + if(elemcnt < 0 || elemsize <= 0 || c == nil) + return -1; + c->f = 0; + c->n = 0; + c->freed = 0; + c->e = elemsize; + c->s = elemcnt; + _threaddebug(DBGCHAN, "chaninit %p", c); + return 1; +} + +Channel* +chancreate(int elemsize, int elemcnt) +{ + Channel *c; + + if(elemcnt < 0 || elemsize <= 0) + return nil; + c = _threadmalloc(sizeof(Channel)+elemsize*elemcnt, 1); + c->e = elemsize; + c->s = elemcnt; + _threaddebug(DBGCHAN, "chancreate %p", c); + return c; +} + +int +alt(Alt *alts) +{ + Alt *a, *xa; + Channel *volatile c; + int n, s; + ulong r; + Thread *t; + + /* + * The point of going splhi here is that note handlers + * might reasonably want to use channel operations, + * but that will hang if the note comes while we hold the + * chanlock. Instead, we delay the note until we've dropped + * the lock. + */ + t = _threadgetproc()->thread; + if(t->moribund || _threadexitsallstatus) + yield(); /* won't return */ + s = _procsplhi(); + lock(&chanlock); + t->alt = alts; + t->chan = Chanalt; + + /* test whether any channels can proceed */ + n = 0; + a = nil; + + for(xa=alts; xa->op!=CHANEND && xa->op!=CHANNOBLK; xa++){ + xa->entryno = -1; + if(xa->op == CHANNOP) + continue; + + c = xa->c; + if(c==nil){ + unlock(&chanlock); + _procsplx(s); + t->chan = Channone; + return -1; + } + if(canexec(xa)) + if(nrand(++n) == 0) + a = xa; + } + + if(a==nil){ + /* nothing can proceed */ + if(xa->op == CHANNOBLK){ + unlock(&chanlock); + _procsplx(s); + t->chan = Channone; +_threadnalt++; + return xa - alts; + } + + /* enqueue on all channels. */ + c = nil; + for(xa=alts; xa->op!=CHANEND; xa++){ + if(xa->op==CHANNOP) + continue; + enqueue(xa, (Channel**)&c); + } + + /* + * wait for successful rendezvous. + * we can't just give up if the rendezvous + * is interrupted -- someone else might come + * along and try to rendezvous with us, so + * we need to be here. + */ + Again: + unlock(&chanlock); + _procsplx(s); + r = _threadrendezvous((ulong)&c, 0); + s = _procsplhi(); + lock(&chanlock); + + if(r==~0){ /* interrupted */ + if(c!=nil) /* someone will meet us; go back */ + goto Again; + c = (Channel*)~0; /* so no one tries to meet us */ + } + + /* dequeue from channels, find selected one */ + a = nil; + for(xa=alts; xa->op!=CHANEND; xa++){ + if(xa->op==CHANNOP) + continue; + if(xa->c == c) + a = xa; + dequeue(xa); + } + unlock(&chanlock); + _procsplx(s); + if(a == nil){ /* we were interrupted */ + assert(c==(Channel*)~0); + return -1; + } + }else{ + altexec(a, s); /* unlocks chanlock, does splx */ + } + _sched(); + t->chan = Channone; +_threadnalt++; + return a - alts; +} + +static int +runop(int op, Channel *c, void *v, int nb) +{ + int r; + Alt a[2]; + + /* + * we could do this without calling alt, + * but the only reason would be performance, + * and i'm not convinced it matters. + */ + a[0].op = op; + a[0].c = c; + a[0].v = v; + a[1].op = CHANEND; + if(nb) + a[1].op = CHANNOBLK; + switch(r=alt(a)){ + case -1: /* interrupted */ + return -1; + case 1: /* nonblocking, didn't accomplish anything */ + assert(nb); + return 0; + case 0: + return 1; + default: + fprint(2, "ERROR: channel alt returned %d\n", r); + abort(); + return -1; + } +} + +int +recv(Channel *c, void *v) +{ + return runop(CHANRCV, c, v, 0); +} + +int +nbrecv(Channel *c, void *v) +{ + return runop(CHANRCV, c, v, 1); +} + +int +send(Channel *c, void *v) +{ + return runop(CHANSND, c, v, 0); +} + +int +nbsend(Channel *c, void *v) +{ + return runop(CHANSND, c, v, 1); +} + +static void +channelsize(Channel *c, int sz) +{ + if(c->e != sz){ + fprint(2, "expected channel with elements of size %d, got size %d", + sz, c->e); + abort(); + } +} + +int +sendul(Channel *c, ulong v) +{ + channelsize(c, sizeof(ulong)); + return send(c, &v); +} + +ulong +recvul(Channel *c) +{ + ulong v; + + channelsize(c, sizeof(ulong)); + if(recv(c, &v) < 0) + return ~0; + return v; +} + +int +sendp(Channel *c, void *v) +{ + channelsize(c, sizeof(void*)); + return send(c, &v); +} + +void* +recvp(Channel *c) +{ + void *v; + + channelsize(c, sizeof(void*)); + if(recv(c, &v) < 0) + return nil; + return v; +} + +int +nbsendul(Channel *c, ulong v) +{ + channelsize(c, sizeof(ulong)); + return nbsend(c, &v); +} + +ulong +nbrecvul(Channel *c) +{ + ulong v; + + channelsize(c, sizeof(ulong)); + if(nbrecv(c, &v) == 0) + return 0; + return v; +} + +int +nbsendp(Channel *c, void *v) +{ + channelsize(c, sizeof(void*)); + return nbsend(c, &v); +} + +void* +nbrecvp(Channel *c) +{ + void *v; + + channelsize(c, sizeof(void*)); + if(nbrecv(c, &v) == 0) + return nil; + return v; +} + +static int +emptyentry(Channel *c) +{ + int i, extra; + + assert((c->nentry==0 && c->qentry==nil) || (c->nentry && c->qentry)); + + for(i=0; i<c->nentry; i++) + if(c->qentry[i]==nil) + return i; + + extra = 16; + c->nentry += extra; +if(c->nentry > _threadhighnentry) _threadhighnentry = c->nentry; + c->qentry = realloc((void*)c->qentry, c->nentry*sizeof(c->qentry[0])); + if(c->qentry == nil) + sysfatal("realloc channel entries: %r"); + _threadmemset(&c->qentry[i], 0, extra*sizeof(c->qentry[0])); + return i; +} + +static void +enqueue(Alt *a, Channel **c) +{ + int i; + + _threaddebug(DBGCHAN, "Queuing alt %p on channel %p", a, a->c); + a->tag = c; + i = emptyentry(a->c); + a->c->qentry[i] = a; +} + +static void +dequeue(Alt *a) +{ + int i; + Channel *c; + + c = a->c; + for(i=0; i<c->nentry; i++) + if(c->qentry[i]==a){ + _threaddebug(DBGCHAN, "Dequeuing alt %p from channel %p", a, a->c); + c->qentry[i] = nil; + if(c->freed) + _chanfree(c); + return; + } +} + +static void* +altexecbuffered(Alt *a, int willreplace) +{ + uchar *v; + Channel *c; + + c = a->c; + /* use buffered channel queue */ + if(a->op==CHANRCV && c->n > 0){ + _threaddebug(DBGCHAN, "buffer recv alt %p chan %p", a, c); + v = c->v + c->e*(c->f%c->s); + if(!willreplace) + c->n--; + c->f++; + return v; + } + if(a->op==CHANSND && c->n < c->s){ + _threaddebug(DBGCHAN, "buffer send alt %p chan %p", a, c); + v = c->v + c->e*((c->f+c->n)%c->s); + if(!willreplace) + c->n++; + return v; + } + abort(); + return nil; +} + +static void +altcopy(void *dst, void *src, int sz) +{ + if(dst){ + if(src) + memmove(dst, src, sz); + else + _threadmemset(dst, 0, sz); + } +} + +static int +altexec(Alt *a, int spl) +{ + volatile Alt *b; + int i, n, otherop; + Channel *c; + void *me, *waiter, *buf; + + c = a->c; + + /* rendezvous with others */ + otherop = (CHANSND+CHANRCV) - a->op; + n = 0; + b = nil; + me = a->v; + for(i=0; i<c->nentry; i++) + if(c->qentry[i] && c->qentry[i]->op==otherop && *c->qentry[i]->tag==nil) + if(nrand(++n) == 0) + b = c->qentry[i]; + if(b != nil){ + _threaddebug(DBGCHAN, "rendez %s alt %p chan %p alt %p", a->op==CHANRCV?"recv":"send", a, c, b); + waiter = b->v; + if(c->s && c->n){ + /* + * if buffer is full and there are waiters + * and we're meeting a waiter, + * we must be receiving. + * + * we use the value in the channel buffer, + * copy the waiter's value into the channel buffer + * on behalf of the waiter, and then wake the waiter. + */ + if(a->op!=CHANRCV) + abort(); + buf = altexecbuffered(a, 1); + altcopy(me, buf, c->e); + altcopy(buf, waiter, c->e); + }else{ + if(a->op==CHANRCV) + altcopy(me, waiter, c->e); + else + altcopy(waiter, me, c->e); + } + *b->tag = c; /* commits us to rendezvous */ + _threaddebug(DBGCHAN, "unlocking the chanlock"); + unlock(&chanlock); + _procsplx(spl); + _threaddebug(DBGCHAN, "chanlock is %lud", *(ulong*)&chanlock); + while(_threadrendezvous((ulong)b->tag, 0) == ~0) + ; + return 1; + } + + buf = altexecbuffered(a, 0); + if(a->op==CHANRCV) + altcopy(me, buf, c->e); + else + altcopy(buf, me, c->e); + + unlock(&chanlock); + _procsplx(spl); + return 1; +} diff --git a/src/libthread/chanprint.c b/src/libthread/chanprint.c new file mode 100644 index 00000000..af9e8103 --- /dev/null +++ b/src/libthread/chanprint.c @@ -0,0 +1,18 @@ +#include "threadimpl.h" + +int +chanprint(Channel *c, char *fmt, ...) +{ + va_list arg; + char *p; + int n; + + va_start(arg, fmt); + p = vsmprint(fmt, arg); + va_end(arg); + if(p == nil) + sysfatal("vsmprint failed: %r"); + n = sendp(c, p); + yield(); /* let recipient handle message immediately */ + return n; +} diff --git a/src/libthread/create.c b/src/libthread/create.c new file mode 100644 index 00000000..ab803a2c --- /dev/null +++ b/src/libthread/create.c @@ -0,0 +1,182 @@ +#include "threadimpl.h" + +#define free +Pqueue _threadpq; + +static int nextID(void); + +/* + * Create and initialize a new Thread structure attached to a given proc. + */ + +typedef struct Stack Stack; +struct Stack { + ulong magic; + Thread *thr; + Stack *next; + uchar buf[STKSIZE-12]; +}; + +static Stack *stkfree; +static Lock stklock; + +void +_stackfree(void *v) +{ + Stack *s; + + s = v; + lock(&stklock); + s->thr = nil; + s->magic = 0; + s->next = stkfree; + stkfree = s; + unlock(&stklock); +} + +static Stack* +stackalloc(void) +{ + char *buf; + Stack *s; + int i; + + lock(&stklock); + while(stkfree == nil){ + unlock(&stklock); + assert(STKSIZE == sizeof(Stack)); + buf = malloc(STKSIZE+128*STKSIZE); + s = (Stack*)(((ulong)buf+STKSIZE)&~(STKSIZE-1)); + for(i=0; i<128; i++) + _stackfree(&s[i]); + lock(&stklock); + } + s = stkfree; + stkfree = stkfree->next; + unlock(&stklock); + s->magic = STKMAGIC; + return s; +} + +static int +newthread(Proc *p, void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp) +{ + int id; + Thread *t; + Stack *s; + + if(stacksize < 32) + sysfatal("bad stacksize %d", stacksize); + t = _threadmalloc(sizeof(Thread), 1); + s = stackalloc(); + s->thr = t; + t->stk = (char*)s; + t->stksize = STKSIZE; + _threaddebugmemset(s->buf, 0xFE, sizeof s->buf); + _threadinitstack(t, f, arg); + t->proc = p; + t->grp = grp; + if(name) + t->cmdname = strdup(name); + t->id = nextID(); + id = t->id; + t->next = (Thread*)~0; + _threaddebug(DBGSCHED, "create thread %d.%d name %s", p->pid, t->id, name); + lock(&p->lock); + p->nthreads++; + if(p->threads.head == nil) + p->threads.head = t; + else{ + t->prevt = p->threads.tail; + t->prevt->nextt = t; + } + p->threads.tail = t; + t->state = Ready; + _threadready(t); + unlock(&p->lock); + return id; +} + +static int +nextID(void) +{ + static Lock l; + static int id; + int i; + + lock(&l); + i = ++id; + unlock(&l); + return i; +} + +int +procrfork(void (*f)(void *), void *arg, uint stacksize, int rforkflag) +{ + Proc *p; + int id; + + p = _threadgetproc(); + assert(p->newproc == nil); + p->newproc = _newproc(f, arg, stacksize, nil, p->thread->grp, rforkflag); + id = p->newproc->threads.head->id; + _sched(); + return id; +} + +int +proccreate(void (*f)(void*), void *arg, uint stacksize) +{ + return procrfork(f, arg, stacksize, 0); +} + +void +_freeproc(Proc *p) +{ + Thread *t, *nextt; + + for(t = p->threads.head; t; t = nextt){ + if(t->cmdname) + free(t->cmdname); + assert(t->stk != nil); + _stackfree((Stack*)t->stk); + nextt = t->nextt; + free(t); + } + free(p); +} + +/* + * Create a new thread and schedule it to run. + * The thread grp is inherited from the currently running thread. + */ +int +threadcreate(void (*f)(void *arg), void *arg, uint stacksize) +{ + return newthread(_threadgetproc(), f, arg, stacksize, nil, threadgetgrp()); +} + +/* + * Create and initialize a new Proc structure with a single Thread + * running inside it. Add the Proc to the global process list. + */ +Proc* +_newproc(void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp, int rforkflag) +{ + Proc *p; + + p = _threadmalloc(sizeof *p, 1); + p->pid = -1; + p->rforkflag = rforkflag; + newthread(p, f, arg, stacksize, name, grp); + + lock(&_threadpq.lock); + if(_threadpq.head == nil) + _threadpq.head = p; + else + *_threadpq.tail = p; + _threadpq.tail = &p->next; + unlock(&_threadpq.lock); + return p; +} + diff --git a/src/libthread/debug.c b/src/libthread/debug.c new file mode 100644 index 00000000..63e2e1b5 --- /dev/null +++ b/src/libthread/debug.c @@ -0,0 +1,48 @@ +#include "threadimpl.h" + +int _threaddebuglevel; + +void +__threaddebug(ulong flag, char *fmt, ...) +{ + char buf[128]; + va_list arg; + Fmt f; + Proc *p; + + if((_threaddebuglevel&flag) == 0) + return; + + fmtfdinit(&f, 2, buf, sizeof buf); + + p = _threadgetproc(); + if(p==nil) + fmtprint(&f, "noproc "); + else if(p->thread) + fmtprint(&f, "%d.%d ", p->pid, p->thread->id); + else + fmtprint(&f, "%d._ ", p->pid); + + va_start(arg, fmt); + fmtvprint(&f, fmt, arg); + va_end(arg); + fmtprint(&f, "\n"); + fmtfdflush(&f); +} + +void +_threadassert(char *s) +{ + char buf[256]; + int n; + Proc *p; + + p = _threadgetproc(); + if(p && p->thread) + n = sprint(buf, "%d.%d ", p->pid, p->thread->id); + else + n = 0; + snprint(buf+n, sizeof(buf)-n, "%s: assertion failed\n", s); + write(2, buf, strlen(buf)); + abort(); +} diff --git a/src/libthread/exec-unix.c b/src/libthread/exec-unix.c new file mode 100644 index 00000000..5a37e34c --- /dev/null +++ b/src/libthread/exec-unix.c @@ -0,0 +1,124 @@ +#include <fcntl.h> +#include <unistd.h> +#include "threadimpl.h" + +void +procexec(Channel *pidc, char *prog, char *args[]) +{ + int n; + Proc *p; + Thread *t; + + _threaddebug(DBGEXEC, "procexec %s", prog); + /* must be only thread in proc */ + p = _threadgetproc(); + t = p->thread; + if(p->threads.head != t || p->threads.head->nextt != nil){ + werrstr("not only thread in proc"); + Bad: + if(pidc) + sendul(pidc, ~0); + return; + } + + /* + * We want procexec to behave like exec; if exec succeeds, + * never return, and if it fails, return with errstr set. + * Unfortunately, the exec happens in another proc since + * we have to wait for the exec'ed process to finish. + * To provide the semantics, we open a pipe with the + * write end close-on-exec and hand it to the proc that + * is doing the exec. If the exec succeeds, the pipe will + * close so that our read below fails. If the exec fails, + * then the proc doing the exec sends the errstr down the + * pipe to us. + */ + if(pipe(p->exec.fd) < 0) + goto Bad; + if(fcntl(p->exec.fd[1], F_SETFD, 1) < 0) + goto Bad; + + /* exec in parallel via the scheduler */ + assert(p->needexec==0); + p->exec.prog = prog; + p->exec.args = args; + p->needexec = 1; + _sched(); + + close(p->exec.fd[1]); + if((n = read(p->exec.fd[0], p->exitstr, ERRMAX-1)) > 0){ /* exec failed */ + p->exitstr[n] = '\0'; + errstr(p->exitstr, ERRMAX); + close(p->exec.fd[0]); + goto Bad; + } + close(p->exec.fd[0]); + + if(pidc) + sendul(pidc, t->ret); + + /* wait for exec'ed program, then exit */ + _schedexecwait(); +} + +void +procexecl(Channel *pidc, char *f, ...) +{ + procexec(pidc, f, &f+1); +} + +void +_schedexecwait(void) +{ + int pid; + Channel *c; + Proc *p; + Thread *t; + Waitmsg *w; + + p = _threadgetproc(); + t = p->thread; + pid = t->ret; + _threaddebug(DBGEXEC, "_schedexecwait %d", t->ret); + + for(;;){ + w = wait(); + if(w == nil) + break; + if(w->pid == pid) + break; + free(w); + } + if(w != nil){ + if((c = _threadwaitchan) != nil) + sendp(c, w); + else + free(w); + } + threadexits("procexec"); +} + +static void +efork(void *ve) +{ + char buf[ERRMAX]; + Execargs *e; + + e = ve; + _threaddebug(DBGEXEC, "_schedexec %s", e->prog); + close(e->fd[0]); + execv(e->prog, e->args); + _threaddebug(DBGEXEC, "_schedexec failed: %r"); + rerrstr(buf, sizeof buf); + if(buf[0]=='\0') + strcpy(buf, "exec failed"); + write(e->fd[1], buf, strlen(buf)); + close(e->fd[1]); + _exits(buf); +} + +int +_schedexec(Execargs *e) +{ + return ffork(RFFDG|RFPROC|RFMEM, efork, e); +} diff --git a/src/libthread/exec.c b/src/libthread/exec.c new file mode 100644 index 00000000..bcf20802 --- /dev/null +++ b/src/libthread/exec.c @@ -0,0 +1,77 @@ +#include "threadimpl.h" + +#define PIPEMNT "/mnt/temp" + +void +procexec(Channel *pidc, char *prog, char *args[]) +{ + int n; + Proc *p; + Thread *t; + + _threaddebug(DBGEXEC, "procexec %s", prog); + /* must be only thread in proc */ + p = _threadgetproc(); + t = p->thread; + if(p->threads.head != t || p->threads.head->nextt != nil){ + werrstr("not only thread in proc"); + Bad: + if(pidc) + sendul(pidc, ~0); + return; + } + + /* + * We want procexec to behave like exec; if exec succeeds, + * never return, and if it fails, return with errstr set. + * Unfortunately, the exec happens in another proc since + * we have to wait for the exec'ed process to finish. + * To provide the semantics, we open a pipe with the + * write end close-on-exec and hand it to the proc that + * is doing the exec. If the exec succeeds, the pipe will + * close so that our read below fails. If the exec fails, + * then the proc doing the exec sends the errstr down the + * pipe to us. + */ + if(bind("#|", PIPEMNT, MREPL) < 0) + goto Bad; + if((p->exec.fd[0] = open(PIPEMNT "/data", OREAD)) < 0){ + unmount(nil, PIPEMNT); + goto Bad; + } + if((p->exec.fd[1] = open(PIPEMNT "/data1", OWRITE|OCEXEC)) < 0){ + close(p->exec.fd[0]); + unmount(nil, PIPEMNT); + goto Bad; + } + unmount(nil, PIPEMNT); + + /* exec in parallel via the scheduler */ + assert(p->needexec==0); + p->exec.prog = prog; + p->exec.args = args; + p->needexec = 1; + _sched(); + + close(p->exec.fd[1]); + if((n = read(p->exec.fd[0], p->exitstr, ERRMAX-1)) > 0){ /* exec failed */ + p->exitstr[n] = '\0'; + errstr(p->exitstr, ERRMAX); + close(p->exec.fd[0]); + goto Bad; + } + close(p->exec.fd[0]); + + if(pidc) + sendul(pidc, t->ret); + + /* wait for exec'ed program, then exit */ + _schedexecwait(); +} + +void +procexecl(Channel *pidc, char *f, ...) +{ + procexec(pidc, f, &f+1); +} + diff --git a/src/libthread/exit.c b/src/libthread/exit.c new file mode 100644 index 00000000..ddf70014 --- /dev/null +++ b/src/libthread/exit.c @@ -0,0 +1,63 @@ +#include "threadimpl.h" +#include <signal.h> + +char *_threadexitsallstatus; +Channel *_threadwaitchan; + +void +threadexits(char *exitstr) +{ + Proc *p; + Thread *t; + + p = _threadgetproc(); + t = p->thread; + t->moribund = 1; + if(exitstr==nil) + exitstr=""; + utfecpy(p->exitstr, p->exitstr+ERRMAX, exitstr); + _sched(); +} + +void +threadexitsall(char *exitstr) +{ + Proc *p; + int *pid; + int i, npid, mypid; + + if(exitstr == nil) + exitstr = ""; + _threadexitsallstatus = exitstr; + _threaddebug(DBGSCHED, "_threadexitsallstatus set to %p", _threadexitsallstatus); + mypid = _threadgetpid(); + + /* + * signal others. + * copying all the pids first avoids other threads + * teardown procedures getting in the way. + */ + lock(&_threadpq.lock); + npid = 0; + for(p=_threadpq.head; p; p=p->next) + npid++; + pid = _threadmalloc(npid*sizeof(pid[0]), 0); + npid = 0; + for(p = _threadpq.head; p; p=p->next) + pid[npid++] = p->pid; + unlock(&_threadpq.lock); + for(i=0; i<npid; i++) + if(pid[i] != mypid) + kill(pid[i], SIGTERM); + + /* leave */ + exit(0); +} + +Channel* +threadwaitchan(void) +{ + if(_threadwaitchan==nil) + _threadwaitchan = chancreate(sizeof(Waitmsg*), 16); + return _threadwaitchan; +} diff --git a/src/libthread/getpid.c b/src/libthread/getpid.c new file mode 100644 index 00000000..da03bd3f --- /dev/null +++ b/src/libthread/getpid.c @@ -0,0 +1,8 @@ +#include "threadimpl.h" +#include <unistd.h> + +int +_threadgetpid(void) +{ + return getpid(); +} diff --git a/src/libthread/id.c b/src/libthread/id.c new file mode 100644 index 00000000..727798d3 --- /dev/null +++ b/src/libthread/id.c @@ -0,0 +1,135 @@ +#include "threadimpl.h" + +int +threadid(void) +{ + return _threadgetproc()->thread->id; +} + +int +threadpid(int id) +{ + int pid; + Proc *p; + Thread *t; + + if (id < 0) + return -1; + if (id == 0) + return _threadgetproc()->pid; + lock(&_threadpq.lock); + for (p = _threadpq.head; p->next; p = p->next){ + lock(&p->lock); + for (t = p->threads.head; t; t = t->nextt) + if (t->id == id){ + pid = p->pid; + unlock(&p->lock); + unlock(&_threadpq.lock); + return pid; + } + unlock(&p->lock); + } + unlock(&_threadpq.lock); + return -1; +} + +int +threadsetgrp(int ng) +{ + int og; + Thread *t; + + t = _threadgetproc()->thread; + og = t->grp; + t->grp = ng; + return og; +} + +int +threadgetgrp(void) +{ + return _threadgetproc()->thread->grp; +} + +void +threadsetname(char *name) +{ +/* + int fd, n; + char buf[128], *s; +*/ + Proc *p; + Thread *t; + + p = _threadgetproc(); + t = p->thread; + if (t->cmdname) + free(t->cmdname); + t->cmdname = strdup(name); +/* Plan 9 only + if(p->nthreads == 1){ + snprint(buf, sizeof buf, "#p/%d/args", getpid()); + if((fd = open(buf, OWRITE)) >= 0){ + snprint(buf, sizeof buf, "%s [%s]", argv0, name); + n = strlen(buf)+1; + s = strchr(buf, ' '); + if(s) + *s = '\0'; + write(fd, buf, n); + close(fd); + } + } +*/ +} + +char* +threadgetname(void) +{ + return _threadgetproc()->thread->cmdname; +} + +void** +threaddata(void) +{ + return &_threadgetproc()->thread->udata[0]; +} + +void** +procdata(void) +{ + return &_threadgetproc()->udata; +} + +static Lock privlock; +static int privmask = 1; + +int +tprivalloc(void) +{ + int i; + + lock(&privlock); + for(i=0; i<NPRIV; i++) + if(!(privmask&(1<<i))){ + privmask |= 1<<i; + unlock(&privlock); + return i; + } + unlock(&privlock); + return -1; +} + +void +tprivfree(int i) +{ + if(i < 0 || i >= NPRIV) + abort(); + lock(&privlock); + privmask &= ~(1<<i); +} + +void** +tprivaddr(int i) +{ + return &_threadgetproc()->thread->udata[i]; +} diff --git a/src/libthread/iocall.c b/src/libthread/iocall.c new file mode 100644 index 00000000..d9cf9d04 --- /dev/null +++ b/src/libthread/iocall.c @@ -0,0 +1,49 @@ +#include "threadimpl.h" + +long +iocall(Ioproc *io, long (*op)(va_list*), ...) +{ + int ret, inted; + Ioproc *msg; + + if(send(io->c, &io) == -1){ + werrstr("interrupted"); + return -1; + } + assert(!io->inuse); + io->inuse = 1; + io->op = op; + va_start(io->arg, op); + msg = io; + inted = 0; + while(send(io->creply, &msg) == -1){ + msg = nil; + inted = 1; + } + if(inted){ + werrstr("interrupted"); + return -1; + } + + /* + * If we get interrupted, we have stick around so that + * the IO proc has someone to talk to. Send it an interrupt + * and try again. + */ + inted = 0; + while(recv(io->creply, nil) == -1){ + inted = 1; + iointerrupt(io); + } + USED(inted); + va_end(io->arg); + ret = io->ret; + if(ret < 0) + errstr(io->err, sizeof io->err); + io->inuse = 0; + + /* release resources */ + while(send(io->creply, &io) == -1) + ; + return ret; +} diff --git a/src/libthread/ioclose.c b/src/libthread/ioclose.c new file mode 100644 index 00000000..fbaabb7c --- /dev/null +++ b/src/libthread/ioclose.c @@ -0,0 +1,16 @@ +#include "threadimpl.h" + +static long +_ioclose(va_list *arg) +{ + int fd; + + fd = va_arg(*arg, int); + return close(fd); +} + +int +ioclose(Ioproc *io, int fd) +{ + return iocall(io, _ioclose, fd); +} diff --git a/src/libthread/iodial.c b/src/libthread/iodial.c new file mode 100644 index 00000000..8171156c --- /dev/null +++ b/src/libthread/iodial.c @@ -0,0 +1,21 @@ +#include "threadimpl.h" + +static long +_iodial(va_list *arg) +{ + char *addr, *local, *dir; + int *cdfp; + + addr = va_arg(*arg, char*); + local = va_arg(*arg, char*); + dir = va_arg(*arg, char*); + cdfp = va_arg(*arg, int*); + + return dial(addr, local, dir, cdfp); +} + +int +iodial(Ioproc *io, char *addr, char *local, char *dir, int *cdfp) +{ + return iocall(io, _iodial, addr, local, dir, cdfp); +} diff --git a/src/libthread/ioopen.c b/src/libthread/ioopen.c new file mode 100644 index 00000000..efca4ca6 --- /dev/null +++ b/src/libthread/ioopen.c @@ -0,0 +1,20 @@ +#include <unistd.h> +#include <fcntl.h> +#include "threadimpl.h" + +static long +_ioopen(va_list *arg) +{ + char *path; + int mode; + + path = va_arg(*arg, char*); + mode = va_arg(*arg, int); + return open(path, mode); +} + +int +ioopen(Ioproc *io, char *path, int mode) +{ + return iocall(io, _ioopen, path, mode); +} diff --git a/src/libthread/ioproc.3 b/src/libthread/ioproc.3 new file mode 100644 index 00000000..1cada2de --- /dev/null +++ b/src/libthread/ioproc.3 @@ -0,0 +1,179 @@ +.TH IOPROC 2 +.SH NAME +closeioproc, +iocall, +ioclose, +iointerrupt, +iodial, +ioopen, +ioproc, +ioread, +ioreadn, +iowrite \- slave I/O processes for threaded programs +.SH SYNOPSIS +.PP +.de XX +.ift .sp 0.5 +.ifn .sp +.. +.EX +.ta \w'Ioproc* 'u +#include <u.h> +#include <libc.h> +#include <thread.h> +.sp +typedef struct Ioproc Ioproc; +.sp +Ioproc* ioproc(void); +.XX +int ioopen(Ioproc *io, char *file, int omode); +int ioclose(Ioproc *io, int fd); +long ioread(Ioproc *io, int fd, void *a, long n); +long ioreadn(Ioproc *io, int fd, void *a, long n); +long iowrite(Ioproc *io, int fd, void *a, long n); +int iodial(Ioproc *io, char *addr, char *local, char *dir, char *cdfp); +.XX +void iointerrupt(Ioproc *io); +void closeioproc(Ioproc *io); +.XX +long iocall(Ioproc *io, long (*op)(va_list *arg), ...); +.EE +.SH DESCRIPTION +.PP +These routines provide access to I/O in slave procs. +Since the I/O itself is done in a slave proc, other threads +in the calling proc can run while the calling thread +waits for the I/O to complete. +.PP +.I Ioproc +forks a new slave proc and returns a pointer to the +.B Ioproc +associated with it. +.I Ioproc +uses +.I mallocz +and +.IR proccreate ; +if either fails, it calls +.I sysfatal +rather than return an error. +.PP +.IR Ioopen , +.IR ioclose , +.IR ioread , +.IR ioreadn , +.IR iowrite , +and +.IR iodial +are execute the +similarly named library or system calls +(see +.IR open (2), +.IR read (2), +and +.IR dial (2)) +in the slave process associated with +.IR io . +It is an error to execute more than one call +at a time in an I/O proc. +.PP +.I Iointerrupt +interrupts the call currently executing in the I/O proc. +If no call is executing, +.IR iointerrupt +is a no-op. +.PP +.I Closeioproc +terminates the I/O proc and frees the associated +.B Ioproc . +.PP +.I Iocall +is a primitive that may be used to implement +more slave I/O routines. +.I Iocall +arranges for +.I op +to be called in +.IR io 's +proc, with +.I arg +set to the variable parameter list, +returning the value that +.I op +returns. +.SH EXAMPLE +Relay messages between two file descriptors, +counting the total number of bytes seen: +.IP +.EX +.ta +\w'xxxx'u +\w'xxxx'u +\w'xxxx'u +int tot; + +void +relaythread(void *v) +{ + int *fd, n; + char buf[1024]; + Ioproc *io; + + fd = v; + io = ioproc(); + while((n = ioread(io, fd[0], buf, sizeof buf)) > 0){ + if(iowrite(io, fd[1], buf, n) != n) + sysfatal("iowrite: %r"); + tot += n; + } + closeioproc(io); +} + +void +relay(int fd0, int fd1) +{ + int fd[4]; + + fd[0] = fd[3] = fd0; + fd[1] = fd[2] = fd1; + threadcreate(relaythread, fd, 8192); + threadcreate(relaythread, fd+2, 8192); +} +.EE +.LP +If the two +.I relaythread +instances were running in different procs, the +common access to +.I tot +would be unsafe. +.EE +.PP +Implement +.IR ioread : +.IP +.EX +static long +_ioread(va_list *arg) +{ + int fd; + void *a; + long n; + + fd = va_arg(*arg, int); + a = va_arg(*arg, void*); + n = va_arg(*arg, long); + return read(fd, a, n); +} + +long +ioread(Ioproc *io, int fd, void *a, long n) +{ + return iocall(io, _ioread, fd, a, n); +} +.EE +.SH SOURCE +.B /sys/src/libthread/io*.c +.SH SEE ALSO +.IR dial (2), +.IR open (2), +.IR read (2), +.IR thread (2) + diff --git a/src/libthread/ioproc.c b/src/libthread/ioproc.c new file mode 100644 index 00000000..2b2a6025 --- /dev/null +++ b/src/libthread/ioproc.c @@ -0,0 +1,74 @@ +#include "threadimpl.h" + +enum +{ + STACK = 8192, +}; + +void +iointerrupt(Ioproc *io) +{ + if(!io->inuse) + return; + threadint(io->tid); +} + +static void +xioproc(void *a) +{ + Ioproc *io, *x; + io = a; + /* + * first recvp acquires the ioproc. + * second tells us that the data is ready. + */ + for(;;){ + while(recv(io->c, &x) == -1) + ; + if(x == 0) /* our cue to leave */ + break; + assert(x == io); + + /* caller is now committed -- even if interrupted he'll return */ + while(recv(io->creply, &x) == -1) + ; + if(x == 0) /* caller backed out */ + continue; + assert(x == io); + + io->ret = io->op(&io->arg); + if(io->ret < 0) + rerrstr(io->err, sizeof io->err); + while(send(io->creply, &io) == -1) + ; + while(recv(io->creply, &x) == -1) + ; + } +} + +Ioproc* +ioproc(void) +{ + Ioproc *io; + + io = mallocz(sizeof(*io), 1); + if(io == nil) + sysfatal("ioproc malloc: %r"); + io->c = chancreate(sizeof(void*), 0); + io->creply = chancreate(sizeof(void*), 0); + io->tid = proccreate(xioproc, io, STACK); + return io; +} + +void +closeioproc(Ioproc *io) +{ + if(io == nil) + return; + iointerrupt(io); + while(send(io->c, 0) == -1) + ; + chanfree(io->c); + chanfree(io->creply); + free(io); +} diff --git a/src/libthread/ioread.c b/src/libthread/ioread.c new file mode 100644 index 00000000..62b1be03 --- /dev/null +++ b/src/libthread/ioread.c @@ -0,0 +1,20 @@ +#include "threadimpl.h" + +static long +_ioread(va_list *arg) +{ + int fd; + void *a; + long n; + + fd = va_arg(*arg, int); + a = va_arg(*arg, void*); + n = va_arg(*arg, long); + return read(fd, a, n); +} + +long +ioread(Ioproc *io, int fd, void *a, long n) +{ + return iocall(io, _ioread, fd, a, n); +} diff --git a/src/libthread/ioreadn.c b/src/libthread/ioreadn.c new file mode 100644 index 00000000..b843f603 --- /dev/null +++ b/src/libthread/ioreadn.c @@ -0,0 +1,21 @@ +#include "threadimpl.h" + +static long +_ioreadn(va_list *arg) +{ + int fd; + void *a; + long n; + + fd = va_arg(*arg, int); + a = va_arg(*arg, void*); + n = va_arg(*arg, long); + n = readn(fd, a, n); + return n; +} + +long +ioreadn(Ioproc *io, int fd, void *a, long n) +{ + return iocall(io, _ioreadn, fd, a, n); +} diff --git a/src/libthread/iosleep.c b/src/libthread/iosleep.c new file mode 100644 index 00000000..55756454 --- /dev/null +++ b/src/libthread/iosleep.c @@ -0,0 +1,16 @@ +#include "threadimpl.h" + +static long +_iosleep(va_list *arg) +{ + long n; + + n = va_arg(*arg, long); + return sleep(n); +} + +int +iosleep(Ioproc *io, long n) +{ + return iocall(io, _iosleep, n); +} diff --git a/src/libthread/iowrite.c b/src/libthread/iowrite.c new file mode 100644 index 00000000..664a84bf --- /dev/null +++ b/src/libthread/iowrite.c @@ -0,0 +1,21 @@ +#include "threadimpl.h" + +static long +_iowrite(va_list *arg) +{ + int fd; + void *a; + long n; + + fd = va_arg(*arg, int); + a = va_arg(*arg, void*); + n = va_arg(*arg, long); + n = write(fd, a, n); + return n; +} + +long +iowrite(Ioproc *io, int fd, void *a, long n) +{ + return iocall(io, _iowrite, fd, a, n); +} diff --git a/src/libthread/kill.c b/src/libthread/kill.c new file mode 100644 index 00000000..a9392ead --- /dev/null +++ b/src/libthread/kill.c @@ -0,0 +1,89 @@ +#include "threadimpl.h" +#include <signal.h> + +static void tinterrupt(Proc*, Thread*); + +static void +threadxxxgrp(int grp, int dokill) +{ + Proc *p; + Thread *t; + + lock(&_threadpq.lock); + for(p=_threadpq.head; p; p=p->next){ + lock(&p->lock); + for(t=p->threads.head; t; t=t->nextt) + if(t->grp == grp){ + if(dokill) + t->moribund = 1; + tinterrupt(p, t); + } + unlock(&p->lock); + } + unlock(&_threadpq.lock); + _threadbreakrendez(); +} + +static void +threadxxx(int id, int dokill) +{ + Proc *p; + Thread *t; + + lock(&_threadpq.lock); + for(p=_threadpq.head; p; p=p->next){ + lock(&p->lock); + for(t=p->threads.head; t; t=t->nextt) + if(t->id == id){ + if(dokill) + t->moribund = 1; + tinterrupt(p, t); + unlock(&p->lock); + unlock(&_threadpq.lock); + _threadbreakrendez(); + return; + } + unlock(&p->lock); + } + unlock(&_threadpq.lock); + _threaddebug(DBGNOTE, "Can't find thread to kill"); + return; +} + +void +threadkillgrp(int grp) +{ + threadxxxgrp(grp, 1); +} + +void +threadkill(int id) +{ + threadxxx(id, 1); +} + +void +threadintgrp(int grp) +{ + threadxxxgrp(grp, 0); +} + +void +threadint(int id) +{ + threadxxx(id, 0); +} + +static void +tinterrupt(Proc *p, Thread *t) +{ + switch(t->state){ + case Running: + kill(p->pid, SIGINT); + // postnote(PNPROC, p->pid, "threadint"); + break; + case Rendezvous: + _threadflagrendez(t); + break; + } +} diff --git a/src/libthread/label.h b/src/libthread/label.h new file mode 100644 index 00000000..0c9f3030 --- /dev/null +++ b/src/libthread/label.h @@ -0,0 +1,24 @@ +/* + * setjmp and longjmp, but our own because some (stupid) c libraries + * assume longjmp is only used to move up the stack, and error out + * if you do otherwise. + */ + +typedef struct Label Label; +#define LABELDPC 0 + +#if defined (__i386__) && (defined(__FreeBSD__) || defined(__linux__)) +struct Label +{ + ulong pc; + ulong bx; + ulong sp; + ulong bp; + ulong si; + ulong di; +}; +#else +#error "Unknown or unsupported architecture" +#endif + + diff --git a/src/libthread/lib.c b/src/libthread/lib.c new file mode 100644 index 00000000..86e7506e --- /dev/null +++ b/src/libthread/lib.c @@ -0,0 +1,35 @@ +#include "threadimpl.h" + +static long totalmalloc; + +void* +_threadmalloc(long size, int z) +{ + void *m; + + m = malloc(size); + if (m == nil) + sysfatal("Malloc of size %ld failed: %r\n", size); + setmalloctag(m, getcallerpc(&size)); + totalmalloc += size; + if (size > 1000000) { + fprint(2, "Malloc of size %ld, total %ld\n", size, totalmalloc); + abort(); + } + if (z) + _threadmemset(m, 0, size); + return m; +} + +void +_threadsysfatal(char *fmt, va_list arg) +{ + char buf[1024]; /* size doesn't matter; we're about to exit */ + + vseprint(buf, buf+sizeof(buf), fmt, arg); + if(argv0) + fprint(2, "%s: %s\n", argv0, buf); + else + fprint(2, "%s\n", buf); + threadexitsall(buf); +} diff --git a/src/libthread/main.c b/src/libthread/main.c new file mode 100644 index 00000000..1acd8348 --- /dev/null +++ b/src/libthread/main.c @@ -0,0 +1,124 @@ +#include "threadimpl.h" +#include <signal.h> + +typedef struct Mainarg Mainarg; +struct Mainarg +{ + int argc; + char **argv; +}; + +int mainstacksize; +int _threadnotefd; +int _threadpasserpid; +static void mainlauncher(void*); +extern void (*_sysfatal)(char*, va_list); + +void +_threaddie(int x) +{ + extern char *_threadexitsallstatus; + USED(x); + + if(_threadexitsallstatus) + exit(_threadexitsallstatus[0] ? 1 : 0); +} + +int +main(int argc, char **argv) +{ + Mainarg *a; + Proc *p; + + signal(SIGTERM, _threaddie); +// rfork(RFREND); + +//_threaddebuglevel = (DBGSCHED|DBGCHAN|DBGREND)^~0; + _systhreadinit(); + _qlockinit(_threadrendezvous); + _sysfatal = _threadsysfatal; +// notify(_threadnote); + if(mainstacksize == 0) + mainstacksize = 32*1024; + + a = _threadmalloc(sizeof *a, 1); + a->argc = argc; + a->argv = argv; + + p = _newproc(mainlauncher, a, mainstacksize, "threadmain", 0, 0); + _schedinit(p); + abort(); /* not reached */ + return 0; +} + +static void +mainlauncher(void *arg) +{ + Mainarg *a; + + a = arg; + threadmain(a->argc, a->argv); + threadexits("threadmain"); +} + +void +_threadsignal(void) +{ +} + +void +_threadsignalpasser(void) +{ +} + +int +_schedfork(Proc *p) +{ + return ffork(RFMEM|RFNOWAIT, _schedinit, p); +} + +void +_schedexit(Proc *p) +{ + char ex[ERRMAX]; + Proc **l; + + lock(&_threadpq.lock); + for(l=&_threadpq.head; *l; l=&(*l)->next){ + if(*l == p){ + *l = p->next; + if(*l == nil) + _threadpq.tail = l; + break; + } + } + unlock(&_threadpq.lock); + + strncpy(ex, p->exitstr, sizeof ex); + ex[sizeof ex-1] = '\0'; + free(p); + _exit(ex[0]); +} + +int +nrand(int n) +{ + return random()%n; +} + +void +_systhreadinit(void) +{ +} + +void +threadstats(void) +{ + extern int _threadnrendez, _threadhighnrendez, + _threadnalt, _threadhighnentry; + fprint(2, "*** THREAD LIBRARY STATS ***\n"); + fprint(2, "nrendez %d high simultaneous %d\n", + _threadnrendez, _threadhighnrendez); + fprint(2, "nalt %d high simultaneous entry %d\n", + _threadnalt, _threadhighnentry); +} diff --git a/src/libthread/memset.c b/src/libthread/memset.c new file mode 100644 index 00000000..dd74fb6c --- /dev/null +++ b/src/libthread/memset.c @@ -0,0 +1,8 @@ +#include "threadimpl.h" +#include <string.h> + +void +_threadmemset(void *v, int c, int n) +{ + memset(v, c, n); +} diff --git a/src/libthread/memsetd.c b/src/libthread/memsetd.c new file mode 100644 index 00000000..ef3f9605 --- /dev/null +++ b/src/libthread/memsetd.c @@ -0,0 +1,8 @@ +#include "threadimpl.h" +#include <string.h> + +void +_threaddebugmemset(void *v, int c, int n) +{ + memset(v, c, n); +} diff --git a/src/libthread/mkfile b/src/libthread/mkfile new file mode 100644 index 00000000..703f6b06 --- /dev/null +++ b/src/libthread/mkfile @@ -0,0 +1,2 @@ +<../libutf/mkfile + diff --git a/src/libthread/note.c b/src/libthread/note.c new file mode 100644 index 00000000..b7f4b137 --- /dev/null +++ b/src/libthread/note.c @@ -0,0 +1,143 @@ +#include "threadimpl.h" + +int _threadnopasser; + +#ifdef NOTDEF +#define NFN 33 +#define ERRLEN 48 +typedef struct Note Note; +struct Note +{ + Lock inuse; + Proc *proc; /* recipient */ + char s[ERRMAX]; /* arg2 */ +}; + +static Note notes[128]; +static Note *enotes = notes+nelem(notes); +static int (*onnote[NFN])(void*, char*); +static int onnotepid[NFN]; +static Lock onnotelock; + +int +threadnotify(int (*f)(void*, char*), int in) +{ + int i, topid; + int (*from)(void*, char*), (*to)(void*, char*); + + if(in){ + from = nil; + to = f; + topid = _threadgetproc()->pid; + }else{ + from = f; + to = nil; + topid = 0; + } + lock(&onnotelock); + for(i=0; i<NFN; i++) + if(onnote[i]==from){ + onnote[i] = to; + onnotepid[i] = topid; + break; + } + unlock(&onnotelock); + return i<NFN; +} + +static void +delayednotes(Proc *p, void *v) +{ + int i; + Note *n; + int (*fn)(void*, char*); + + if(!p->pending) + return; + + p->pending = 0; + for(n=notes; n<enotes; n++){ + if(n->proc == p){ + for(i=0; i<NFN; i++){ + if(onnotepid[i]!=p->pid || (fn = onnote[i])==nil) + continue; + if((*fn)(v, n->s)) + break; + } + if(i==NFN){ + _threaddebug(DBGNOTE, "Unhandled note %s, proc %p\n", n->s, p); + if(v != nil) + noted(NDFLT); + else if(strncmp(n->s, "sys:", 4)==0) + abort(); + threadexitsall(n->s); + } + n->proc = nil; + unlock(&n->inuse); + } + } +} + +void +_threadnote(void *v, char *s) +{ + Proc *p; + Note *n; + + _threaddebug(DBGNOTE, "Got note %s", s); + if(strncmp(s, "sys:", 4) == 0) + noted(NDFLT); + +// if(_threadexitsallstatus){ +// _threaddebug(DBGNOTE, "Threadexitsallstatus = '%s'\n", _threadexitsallstatus); +// _exits(_threadexitsallstatus); +// } + + if(strcmp(s, "threadint")==0) + noted(NCONT); + + p = _threadgetproc(); + if(p == nil) + noted(NDFLT); + + for(n=notes; n<enotes; n++) + if(canlock(&n->inuse)) + break; + if(n==enotes) + sysfatal("libthread: too many delayed notes"); + utfecpy(n->s, n->s+ERRMAX, s); + n->proc = p; + p->pending = 1; + if(!p->splhi) + delayednotes(p, v); + noted(NCONT); +} +#endif + +int +_procsplhi(void) +{ + int s; + Proc *p; + + p = _threadgetproc(); + s = p->splhi; + p->splhi = 1; + return s; +} + +void +_procsplx(int s) +{ + Proc *p; + + p = _threadgetproc(); + p->splhi = s; + if(s) + return; +/* + if(p->pending) + delayednotes(p, nil); +*/ +} + diff --git a/src/libthread/proctab.c b/src/libthread/proctab.c new file mode 100644 index 00000000..4029652a --- /dev/null +++ b/src/libthread/proctab.c @@ -0,0 +1,64 @@ +#include "threadimpl.h" + +/* this will need work */ +enum +{ + PTABHASH = 257, +}; + +static Lock ptablock; +Proc *ptab[PTABHASH]; + +void +_threadsetproc(Proc *p) +{ + int h; + + lock(&ptablock); + h = ((unsigned)p->pid)%PTABHASH; + p->link = ptab[h]; + unlock(&ptablock); + ptab[h] = p; +} + +static Proc* +__threadgetproc(int rm) +{ + Proc **l, *p; + int h, pid; + Thread *t; + ulong *s; + + s = (ulong*)((ulong)&pid & ~(STKSIZE-1)); + if(s[0] == STKMAGIC){ + t = (Thread*)s[1]; + return t->proc; + } + + pid = _threadgetpid(); + + lock(&ptablock); + h = ((unsigned)pid)%PTABHASH; + for(l=&ptab[h]; p=*l; l=&p->link){ + if(p->pid == pid){ + if(rm) + *l = p->link; + unlock(&ptablock); + return p; + } + } + unlock(&ptablock); + return nil; +} + +Proc* +_threadgetproc(void) +{ + return __threadgetproc(0); +} + +Proc* +_threaddelproc(void) +{ + return __threadgetproc(1); +} diff --git a/src/libthread/ref.c b/src/libthread/ref.c new file mode 100644 index 00000000..9b78e63e --- /dev/null +++ b/src/libthread/ref.c @@ -0,0 +1,13 @@ +#include "threadimpl.h" + +void +incref(Ref *r) +{ + _xinc(&r->ref); +} + +long +decref(Ref *r) +{ + return _xdec(&r->ref); +} diff --git a/src/libthread/rendez.c b/src/libthread/rendez.c new file mode 100644 index 00000000..62b825b5 --- /dev/null +++ b/src/libthread/rendez.c @@ -0,0 +1,104 @@ +#include "threadimpl.h" + +Rgrp _threadrgrp; +static int isdirty; +int _threadhighnrendez; +int _threadnrendez; +static int nrendez; + +static ulong +finish(Thread *t, ulong val) +{ + ulong ret; + + ret = t->rendval; + t->rendval = val; + while(t->state == Running) + sleep(0); + lock(&t->proc->lock); + if(t->state == Rendezvous){ /* not always true: might be Dead */ + t->state = Ready; + _threadready(t); + } + unlock(&t->proc->lock); + return ret; +} + +ulong +_threadrendezvous(ulong tag, ulong val) +{ + ulong ret; + Thread *t, **l; + + lock(&_threadrgrp.lock); +_threadnrendez++; + l = &_threadrgrp.hash[tag%nelem(_threadrgrp.hash)]; + for(t=*l; t; l=&t->rendhash, t=*l){ + if(t->rendtag==tag){ + _threaddebug(DBGREND, "Rendezvous with thread %d.%d", t->proc->pid, t->id); + *l = t->rendhash; + ret = finish(t, val); + --nrendez; + unlock(&_threadrgrp.lock); + return ret; + } + } + + /* Going to sleep here. */ + t = _threadgetproc()->thread; + t->rendbreak = 0; + t->inrendez = 1; + t->rendtag = tag; + t->rendval = val; + t->rendhash = *l; + *l = t; + t->nextstate = Rendezvous; + ++nrendez; + if(nrendez > _threadhighnrendez) + _threadhighnrendez = nrendez; + _threaddebug(DBGREND, "Rendezvous for tag %lud", t->rendtag); + unlock(&_threadrgrp.lock); + _sched(); + t->inrendez = 0; + _threaddebug(DBGREND, "Woke after rendezvous; val is %lud", t->rendval); + return t->rendval; +} + +/* + * This is called while holding _threadpq.lock and p->lock, + * so we can't lock _threadrgrp.lock. Instead our caller has + * to call _threadbreakrendez after dropping those locks. + */ +void +_threadflagrendez(Thread *t) +{ + t->rendbreak = 1; + isdirty = 1; +} + +void +_threadbreakrendez(void) +{ + int i; + Thread *t, **l; + + if(isdirty == 0) + return; + lock(&_threadrgrp.lock); + if(isdirty == 0){ + unlock(&_threadrgrp.lock); + return; + } + isdirty = 0; + for(i=0; i<nelem(_threadrgrp.hash); i++){ + l = &_threadrgrp.hash[i]; + for(t=*l; t; t=*l){ + if(t->rendbreak){ + *l = t->rendhash; + finish(t, ~0); + }else + l=&t->rendhash; + } + } + unlock(&_threadrgrp.lock); +} diff --git a/src/libthread/rpm.spec b/src/libthread/rpm.spec new file mode 100644 index 00000000..fdc72de2 --- /dev/null +++ b/src/libthread/rpm.spec @@ -0,0 +1,26 @@ +Summary: Port of Plan 9's thread library +Name: libthread +Version: 2.0 +Release: 1 +Group: Development/C +Copyright: BSD-like +Packager: Russ Cox <rsc@post.harvard.edu> +Source: http://pdos.lcs.mit.edu/~rsc/software/libthread-2.0.tgz +URL: http://pdos.lcs.mit.edu/~rsc/software/#libthread + +%description +Libthread is a port of Plan 9's thread library +%prep +%setup + +%build +make + +%install +make install + +%files +/usr/local/include/thread.h +/usr/local/lib/libthread.a +/usr/local/man/man3/thread.3 +/usr/local/man/man3/ioproc.3 diff --git a/src/libthread/sched.c b/src/libthread/sched.c new file mode 100644 index 00000000..8609833d --- /dev/null +++ b/src/libthread/sched.c @@ -0,0 +1,192 @@ +#include "threadimpl.h" +#include <signal.h> + +//static Thread *runthread(Proc*); + +static char *_psstate[] = { + "Dead", + "Running", + "Ready", + "Rendezvous", +}; + +static char* +psstate(int s) +{ + if(s < 0 || s >= nelem(_psstate)) + return "unknown"; + return _psstate[s]; +} + +void +_schedinit(void *arg) +{ + Proc *p; + Thread *t; + extern void ignusr1(void), _threaddie(int); + ignusr1(); + signal(SIGTERM, _threaddie); + + + + p = arg; + p->pid = _threadgetpid(); + _threadsetproc(p); + while(_setlabel(&p->sched)) + ; + _threaddebug(DBGSCHED, "top of schedinit, _threadexitsallstatus=%p", _threadexitsallstatus); + if(_threadexitsallstatus) + exits(_threadexitsallstatus); + lock(&p->lock); + if((t=p->thread) != nil){ + p->thread = nil; + if(t->moribund){ + assert(t->moribund == 1); + t->state = Dead; + if(t->prevt) + t->prevt->nextt = t->nextt; + else + p->threads.head = t->nextt; + if(t->nextt) + t->nextt->prevt = t->prevt; + else + p->threads.tail = t->prevt; + unlock(&p->lock); + if(t->inrendez){ + _threadflagrendez(t); + _threadbreakrendez(); + } + _stackfree(t->stk); + free(t->cmdname); + free(t); /* XXX how do we know there are no references? */ + t = nil; + _sched(); + } + if(p->needexec){ + t->ret = _schedexec(&p->exec); + p->needexec = 0; + } + if(p->newproc){ + t->ret = _schedfork(p->newproc); + if(t->ret < 0){ +//fprint(2, "_schedfork: %r\n"); + abort(); +} + p->newproc = nil; + } + t->state = t->nextstate; + if(t->state == Ready) + _threadready(t); + } + unlock(&p->lock); + _sched(); +} + +static inline Thread* +runthread(Proc *p) +{ + Thread *t; + Tqueue *q; + + if(p->nthreads==0) + return nil; + q = &p->ready; + lock(&p->readylock); + if(q->head == nil){ + q->asleep = 1; + _threaddebug(DBGSCHED, "sleeping for more work"); + unlock(&p->readylock); + while(rendezvous((ulong)q, 0) == ~0){ + if(_threadexitsallstatus) + exits(_threadexitsallstatus); + } + /* lock picked up from _threadready */ + } + t = q->head; + q->head = t->next; + unlock(&p->readylock); + return t; +} + +void +_sched(void) +{ + Proc *p; + Thread *t; + +Resched: + p = _threadgetproc(); +//fprint(2, "p %p\n", p); + if((t = p->thread) != nil){ + if((ulong)&p < (ulong)t->stk){ /* stack overflow */ + fprint(2, "stack overflow %lux %lux\n", (ulong)&p, (ulong)t->stk); + abort(); + } + // _threaddebug(DBGSCHED, "pausing, state=%s set %p goto %p", + // psstate(t->state), &t->sched, &p->sched); + if(_setlabel(&t->sched)==0) + _gotolabel(&p->sched); + return; + }else{ + t = runthread(p); + if(t == nil){ + _threaddebug(DBGSCHED, "all threads gone; exiting"); + _threaddelproc(); + _schedexit(p); + } + // _threaddebug(DBGSCHED, "running %d.%d", t->proc->pid, t->id); + p->thread = t; + if(t->moribund){ + _threaddebug(DBGSCHED, "%d.%d marked to die"); + goto Resched; + } + t->state = Running; + t->nextstate = Ready; + _gotolabel(&t->sched); + } +} + +long +threadstack(void) +{ + Proc *p; + Thread *t; + + p = _threadgetproc(); + t = p->thread; + return (ulong)&p - (ulong)t->stk; +} + +void +_threadready(Thread *t) +{ + Tqueue *q; + + assert(t->state == Ready); + _threaddebug(DBGSCHED, "readying %d.%d", t->proc->pid, t->id); + q = &t->proc->ready; + lock(&t->proc->readylock); + t->next = nil; + if(q->head==nil) + q->head = t; + else + q->tail->next = t; + q->tail = t; + if(q->asleep){ + q->asleep = 0; + /* lock passes to runthread */ + _threaddebug(DBGSCHED, "waking process %d", t->proc->pid); + while(rendezvous((ulong)q, 0) == ~0){ + if(_threadexitsallstatus) + exits(_threadexitsallstatus); + } + }else + unlock(&t->proc->readylock); +} + +void +yield(void) +{ + _sched(); +} + diff --git a/src/libthread/texec.c b/src/libthread/texec.c new file mode 100644 index 00000000..9ba86827 --- /dev/null +++ b/src/libthread/texec.c @@ -0,0 +1,34 @@ +#include <lib9.h> +#include <thread.h> +extern int _threaddebuglevel; + +void +doexec(void *v) +{ + char **argv = v; + + procexec(nil, argv[0], argv); + sendp(threadwaitchan(), nil); +} + +void +threadmain(int argc, char **argv) +{ + Channel *c; + Waitmsg *w; + + ARGBEGIN{ + case 'D': + _threaddebuglevel = ~0; + break; + }ARGEND + + c = threadwaitchan(); + proccreate(doexec, argv, 8192); + w = recvp(c); + if(w == nil) + print("exec failed\n"); + else + print("%d %lu %lu %lu %s\n", w->pid, w->time[0], w->time[1], w->time[2], w->msg); + threadexits(nil); +} diff --git a/src/libthread/thread.3 b/src/libthread/thread.3 new file mode 100644 index 00000000..3009ac84 --- /dev/null +++ b/src/libthread/thread.3 @@ -0,0 +1,576 @@ +.TH THREAD 2 +.SH NAME +alt, +chancreate, +chanfree, +chaninit, +chanprint, +mainstacksize, +proccreate, +procdata, +procexec, +procexecl, +procrfork, +recv, +recvp, +recvul, +send, +sendp, +sendul, +nbrecv, +nbrecvp, +nbrecvul, +nbsend, +nbsendp, +nbsendul, +threadcreate, +threaddata, +threadexits, +threadexitsall, +threadgetgrp, +threadgetname, +threadint, +threadintgrp, +threadkill, +threadkillgrp, +threadmain, +threadnotify, +threadid, +threadpid, +threadsetgrp, +threadsetname, +threadwaitchan, +yield \- thread and proc management +.SH SYNOPSIS +.PP +.EX +.ta 4n +4n +4n +4n +4n +4n +4n +#include <u.h> +#include <libc.h> +#include <thread.h> +.sp +#define CHANEND 0 +#define CHANSND 1 +#define CHANRCV 2 +#define CHANNOP 3 +#define CHANNOBLK 4 +.sp +.ta \w' 'u +\w'Channel 'u +typedef struct Alt Alt; +struct Alt { + Channel *c; + void *v; + int op; + Channel **tag; + int entryno; +}; +.fi +.de XX +.ift .sp 0.5 +.ifn .sp +.. +.PP +.nf +.ft L +.ta \w'\fLChannel* 'u +4n +4n +4n +4n +void threadmain(int argc, char *argv[]) +int mainstacksize +int proccreate(void (*fn)(void*), void *arg, uint stacksize) +int procrfork(void (*fn)(void*), void *arg, uint stacksize, + int rforkflag) +int threadcreate(void (*fn)(void*), void *arg, uint stacksize) +void threadexits(char *status) +void threadexitsall(char *status) +void yield(void) +.XX +int threadid(void) +int threadgrp(void) +int threadsetgrp(int group) +int threadpid(int id) +.XX +int threadint(int id) +int threadintgrp(int group) +int threadkill(int id) +int threadkillgrp(int group) +.XX +void threadsetname(char *name) +char* threadgetname(void) +.XX +void** threaddata(void) +void** procdata(void) +.XX +int chaninit(Channel *c, int elsize, int nel) +Channel* chancreate(int elsize, int nel) +void chanfree(Channel *c) +.XX +int alt(Alt *alts) +int recv(Channel *c, void *v) +void* recvp(Channel *c) +ulong recvul(Channel *c) +int nbrecv(Channel *c, void *v) +void* nbrecvp(Channel *c) +ulong nbrecvul(Channel *c) +int send(Channel *c, void *v) +int sendp(Channel *c, void *v) +int sendul(Channel *c, ulong v) +int nbsend(Channel *c, void *v) +int nbsendp(Channel *c, void *v) +int nbsendul(Channel *c, ulong v) +int chanprint(Channel *c, char *fmt, ...) +.XX +int procexecl(Channel *cpid, char *file, ...) +int procexec(Channel *cpid, char *file, char *args[]) +Channel* threadwaitchan(void) +.XX +int threadnotify(int (*f)(void*, char*), int in) +.EE +.SH DESCRIPTION +.PP +The thread library provides parallel programming support similar to that +of the languages +Alef and Newsqueak. +Threads +and +procs +occupy a shared address space, +communicating and synchronizing through +.I channels +and shared variables. +.PP +A +.I proc +is a Plan 9 process that contains one or more cooperatively scheduled +.IR threads . +Programs using threads must replace +.I main +by +.IR threadmain . +The thread library provides a +.I main +function that sets up a proc with a single thread executing +.I threadmain +on a stack of size +.I mainstacksize +(default eight kilobytes). +To set +.IR mainstacksize , +declare a global variable +initialized to the desired value +.RI ( e.g. , +.B int +.B mainstacksize +.B = +.BR 1024 ). +.PP +.I Threadcreate +creates a new thread in the calling proc, returning a unique integer +identifying the thread; the thread +executes +.I fn(arg) +on a stack of size +.IR stacksize . +Thread stacks are allocated in shared memory, making it valid to pass +pointers to stack variables between threads and procs. +.I Procrfork +creates a new proc, and inside that proc creates +a single thread as +.I threadcreate +would, +returning the id of the created thread. +.I Procrfork +creates the new proc by calling +.B rfork +(see +.IR fork (2)) +with flags +.BR RFPROC|RFMEM|RFNOWAIT| \fIrforkflag\fR. +(The thread library depends on all its procs +running in the same rendezvous group. +Do not include +.B RFREND +in +.IR rforkflag .) +.I Proccreate +is identical to +.I procrfork +with +.I rforkflag +set to zero. +Be aware that the calling thread may continue +execution before +the newly created proc and thread +are scheduled. +Because of this, +.I arg +should not point to data on the stack of a function that could +return before the new process is scheduled. +.PP +.I Threadexits +terminates the calling thread. +If the thread is the last in its proc, +.I threadexits +also terminates the proc, using +.I status +as the exit status. +.I Threadexitsall +terminates all procs in the program, +using +.I status +as the exit status. +.PP +The threads in a proc are coroutines, scheduled nonpreemptively +in a round-robin fashion. +A thread must explicitly relinquish control of the processor +before another thread in the same proc is run. +Calls that do this are +.IR yield , +.IR proccreate , +.IR procexec , +.IR procexecl , +.IR threadexits , +.IR alt , +.IR send , +and +.I recv +(and the calls related to +.I send +and +.IR recv \(emsee +their descriptions further on). +Procs are scheduled by the operating system. +Therefore, threads in different procs can preempt one another +in arbitrary ways and should synchronize their +actions using +.B qlocks +(see +.IR lock (2)) +or channel communication. +System calls such as +.IR read (2) +block the entire proc; +all threads in a proc block until the system call finishes. +.PP +As mentioned above, each thread has a unique integer thread id. +Thread ids are not reused; they are unique across the life of the program. +.I Threadid +returns the id for the current thread. +Each thread also has a thread group id. +The initial thread has a group id of zero. +Each new thread inherits the group id of +the thread that created it. +.I Threadgrp +returns the group id for the current thread; +.I threadsetgrp +sets it. +.I Threadpid +returns the pid of the Plan 9 process containing +the thread identified by +.IR id , +or \-1 +if no such thread is found. +.PP +.I Threadint +interrupts a thread that is blocked in a channel operation +or system call. +.I Threadintgrp +interrupts all threads with the given group id. +.I Threadkill +marks a thread to die when it next relinquishes the processor +(via one of the calls listed above). +If the thread is blocked in a channel operation or system call, +it is also interrupted. +.I Threadkillgrp +kills all threads with the given group id. +Note that +.I threadkill +and +.I threadkillgrp +will not terminate a thread that never relinquishes +the processor. +.PP +Primarily for debugging, +threads can have string names associated with them. +.I Threadgetname +returns the current thread's name; +.I threadsetname +sets it. +The pointer returned by +.I threadgetname +is only valid until the next call to +.IR threadsetname . +.PP +.I Threaddata +returns a pointer to a per-thread pointer +that may be modified by threaded programs for +per-thread storage. +Similarly, +.I procdata +returns a pointer to a per-proc pointer. +.PP +.I Procexecl +and +.I procexec +are threaded analogues of +.I exec +and +.I execl +(see +.IR exec (2)); +on success, +they replace the calling thread (which must be the only thread in its proc) +and invoke the external program, never returning. +On error, they return \-1. +If +.I cpid +is not null, the pid of the invoked program +will be sent along +.I cpid +once the program has been started, or \-1 will be sent if an +error occurs. +.I Procexec +and +.I procexecl +will not access their arguments after sending a result +along +.IR cpid . +Thus, programs that malloc the +.I argv +passed to +.I procexec +can safely free it once they have +received the +.I cpid +response. +.I Threadwaitchan +returns a channel of pointers to +.B Waitmsg +structures (see +.IR wait (2)). +When an exec'ed process exits, a pointer to a +.B Waitmsg +is sent to this channel. +These +.B Waitmsg +structures have been allocated with +.IR malloc (2) +and should be freed after use. +.PP +A +.B Channel +is a buffered or unbuffered queue for fixed-size messages. +Procs and threads +.I send +messages into the channel and +.I recv +messages from the channel. If the channel is unbuffered, a +.I send +operation blocks until the corresponding +.I recv +operation occurs and +.IR "vice versa" . +.I Chaninit +initializes a +.B Channel +for messages of size +.I elsize +and with a buffer holding +.I nel +messages. +If +.I nel +is zero, the channel is unbuffered. +.IR Chancreate +allocates a new channel and initializes it. +.I Chanfree +frees a channel that is no longer used. +.I Chanfree +can be called by either sender or receiver after the last item has been +sent or received. Freeing the channel will be delayed if there is a thread +blocked on it until that thread unblocks (but +.I chanfree +returns immediately). +.PP +.I Send +sends the element pointed at by +.I v +to the channel +.IR c . +If +.I v +is null, zeros are sent. +.I Recv +receives an element from +.I c +and stores it in +.IR v . +If +.I v +is null, +the received value is discarded. +.I Send +and +.I recv +return 1 on success, \-1 if interrupted. +.I Nbsend +and +.I nbrecv +behave similarly, but return 0 rather than blocking. +.PP +.IR Sendp , +.IR nbsendp , +.IR sendul , +and +.I nbsendul +send a pointer or an unsigned long; the channel must +have been initialized with the appropriate +.IR elsize . +.IR Recvp , +.IR nbrecvp , +.IR recvul , +and +.I nbrecvul +receive a pointer or an unsigned long; +they return zero when a zero is received, +when interrupted, or +(for +.I nbrecvp +and +.IR nbrecvul ) +when the operation would have blocked. +To distinguish between these three cases, +use +.I recv +or +.IR nbrecv . +.PP +.I Alt +can be used to recv from or send to one of a number of channels, +as directed by an array of +.B Alt +structures, +each of which describes a potential send or receive operation. +In an +.B Alt +structure, +.B c +is the channel; +.B v +the value pointer (which may be null); and +.B op +the operation: +.B CHANSND +for a send operation, +.B CHANRECV +for a recv operation; +.B CHANNOP +for no operation +(useful +when +.I alt +is called with a varying set of operations). +The array of +.B Alt +structures is terminated by an entry with +.I op +.B CHANEND +or +.BR CHANNOBLK . +If at least one +.B Alt +structure can proceed, one of them is +chosen at random to be executed. +.I Alt +returns the index of the chosen structure. +If no operations can proceed and the list is terminated with +.BR CHANNOBLK , +.I alt +returns the index of the terminating +.B CHANNOBLK +structure. +Otherwise, +.I alt +blocks until one of the operations can proceed, +eventually returning the index of the structure executes. +.I Alt +returns \-1 when interrupted. +The +.B tag +and +.B entryno +fields in the +.B Alt +structure are used internally by +.I alt +and need not be initialized. +They are not used between +.I alt +calls. +.PP +.I Chanprint +formats its arguments in the manner of +.IR print (2) +and sends the result to the channel +.IR c. +The string delivered by +.I chanprint +is allocated with +.IR malloc (2) +and should be freed upon receipt. +.PP +Thread library functions do not return on failure; +if errors occur, the entire program is aborted. +.PP +Threaded programs should use +.I threadnotify +in place of +.I atnotify +(see +.IR notify (2)). +.PP +It is safe to use +.B sysfatal +(see +.IR perror (2)) +in threaded programs. +.I Sysfatal +will print the error string and call +.IR threadexitsall . +.PP +It is safe to use +.IR rfork +(see +.IR fork (2)) +to manage the namespace, file descriptors, note group, and environment of a +single process. +That is, it is safe to call +.I rfork +with the flags +.BR RFNAMEG , +.BR RFFDG , +.BR RFCFDG , +.BR RFNOTEG , +.BR RFENVG , +and +.BR RFCENVG. +(To create new processes, use +.I proccreate +and +.IR procrfork .) +As mentioned above, +the thread library depends on all procs being in the +same rendezvous group; do not change the rendezvous +group with +.IR rfork . +.SH FILES +.B /sys/lib/acid/thread +contains useful +.IR acid (1) +functions for debugging threaded programs. +.PP +.B /sys/src/libthread/example.c +contains a full example program. +.SH SOURCE +.B /sys/src/libthread +.SH SEE ALSO +.IR intro (2), +.IR ioproc (2) diff --git a/src/libthread/thread.h b/src/libthread/thread.h new file mode 100644 index 00000000..10aac284 --- /dev/null +++ b/src/libthread/thread.h @@ -0,0 +1,132 @@ +#ifndef _THREADH_ +#define _THREADH_ 1 + +/* avoid conflicts with socket library */ +#undef send +#define send _threadsend +#undef recv +#define recv _threadrecv + +typedef struct Alt Alt; +typedef struct Channel Channel; +typedef struct Ref Ref; + +/* Channel structure. S is the size of the buffer. For unbuffered channels + * s is zero. v is an array of s values. If s is zero, v is unused. + * f and n represent the state of the queue pointed to by v. + */ + +enum { + Nqwds = 2, + Nqshift = 5, // 2log #of bits in long + Nqmask = - 1, + Nqbits = (1 << Nqshift) * 2, +}; + +struct Channel { + int s; // Size of the channel (may be zero) + unsigned int f; // Extraction point (insertion pt: (f + n) % s) + unsigned int n; // Number of values in the channel + int e; // Element size + int freed; // Set when channel is being deleted + volatile Alt **qentry; // Receivers/senders waiting (malloc) + volatile int nentry; // # of entries malloc-ed + unsigned char v[1]; // Array of s values in the channel +}; + + +/* Channel operations for alt: */ +typedef enum { + CHANEND, + CHANSND, + CHANRCV, + CHANNOP, + CHANNOBLK, +} ChanOp; + +struct Alt { + Channel *c; /* channel */ + void *v; /* pointer to value */ + ChanOp op; /* operation */ + + /* the next variables are used internally to alt + * they need not be initialized + */ + Channel **tag; /* pointer to rendez-vous tag */ + int entryno; /* entry number */ +}; + +struct Ref { + long ref; +}; + +int alt(Alt alts[]); +Channel* chancreate(int elemsize, int bufsize); +int chaninit(Channel *c, int elemsize, int elemcnt); +void chanfree(Channel *c); +int chanprint(Channel *, char *, ...); +long decref(Ref *r); /* returns 0 iff value is now zero */ +void incref(Ref *r); +int nbrecv(Channel *c, void *v); +void* nbrecvp(Channel *c); +unsigned long nbrecvul(Channel *c); +int nbsend(Channel *c, void *v); +int nbsendp(Channel *c, void *v); +int nbsendul(Channel *c, unsigned long v); +int proccreate(void (*f)(void *arg), void *arg, unsigned int stacksize); +int procrfork(void (*f)(void *arg), void *arg, unsigned int stacksize, int flag); +void** procdata(void); +void procexec(Channel *, char *, char *[]); +void procexecl(Channel *, char *, ...); +int recv(Channel *c, void *v); +void* recvp(Channel *c); +unsigned long recvul(Channel *c); +int send(Channel *c, void *v); +int sendp(Channel *c, void *v); +int sendul(Channel *c, unsigned long v); +int threadcreate(void (*f)(void *arg), void *arg, unsigned int stacksize); +void** threaddata(void); +void threadexits(char *); +void threadexitsall(char *); +int threadgetgrp(void); /* return thread group of current thread */ +char* threadgetname(void); +void threadint(int); /* interrupt thread */ +void threadintgrp(int); /* interrupt threads in grp */ +void threadkill(int); /* kill thread */ +void threadkillgrp(int); /* kill threads in group */ +void threadmain(int argc, char *argv[]); +void threadnonotes(void); +int threadnotify(int (*f)(void*, char*), int in); +int threadid(void); +int threadpid(int); +int threadsetgrp(int); /* set thread group, return old */ +void threadsetname(char *name); +Channel* threadwaitchan(void); +int tprivalloc(void); +void tprivfree(int); +void **tprivaddr(int); +void yield(void); + +long threadstack(void); + +extern int mainstacksize; + +/* slave I/O processes */ +typedef struct Ioproc Ioproc; + +Ioproc* ioproc(void); +void closeioproc(Ioproc*); +void iointerrupt(Ioproc*); + +int ioclose(Ioproc*, int); +int iodial(Ioproc*, char*, char*, char*, int*); +int ioopen(Ioproc*, char*, int); +long ioread(Ioproc*, int, void*, long); +long ioreadn(Ioproc*, int, void*, long); +long iowrite(Ioproc*, int, void*, long); +int iosleep(Ioproc*, long); + +long iocall(Ioproc*, long (*)(va_list*), ...); +void ioret(Ioproc*, int); + +#endif /* _THREADH_ */ diff --git a/src/libthread/threadimpl.h b/src/libthread/threadimpl.h new file mode 100644 index 00000000..24d9b214 --- /dev/null +++ b/src/libthread/threadimpl.h @@ -0,0 +1,219 @@ +/* + * Some notes on locking: + * + * All the locking woes come from implementing + * threadinterrupt (and threadkill). + * + * _threadgetproc()->thread is always a live pointer. + * p->threads, p->ready, and _threadrgrp also contain + * live thread pointers. These may only be consulted + * while holding p->lock or _threadrgrp.lock; in procs + * other than p, the pointers are only guaranteed to be live + * while the lock is still being held. + * + * Thread structures can only be freed by the proc + * they belong to. Threads marked with t->inrendez + * need to be extracted from the _threadrgrp before + * being freed. + * + * _threadrgrp.lock cannot be acquired while holding p->lock. + */ + +#include <assert.h> +#include <lib9.h> +#include <thread.h> +#include "label.h" + +enum{ +STKSIZE = 16384, +STKMAGIC = 0xCAFEBEEF +}; + +typedef struct Thread Thread; +typedef struct Proc Proc; +typedef struct Tqueue Tqueue; +typedef struct Pqueue Pqueue; +typedef struct Rgrp Rgrp; +typedef struct Execargs Execargs; + +/* must match list in sched.c */ +typedef enum +{ + Dead, + Running, + Ready, + Rendezvous, +} State; + +typedef enum +{ + Channone, + Chanalt, + Chansend, + Chanrecv, +} Chanstate; + +enum +{ + RENDHASH = 10009, + Printsize = 2048, + NPRIV = 8, +}; + +struct Rgrp +{ + Lock lock; + Thread *hash[RENDHASH]; +}; + +struct Tqueue /* Thread queue */ +{ + int asleep; + Thread *head; + Thread *tail; +}; + +struct Thread +{ + Lock lock; /* protects thread data structure */ + Label sched; /* for context switches */ + int id; /* thread id */ + int grp; /* thread group */ + int moribund; /* thread needs to die */ + State state; /* run state */ + State nextstate; /* next run state */ + uchar *stk; /* top of stack (lowest address of stack) */ + uint stksize; /* stack size */ + Thread *next; /* next on ready queue */ + + Proc *proc; /* proc of this thread */ + Thread *nextt; /* next on list of threads in this proc */ + Thread *prevt; /* prev on list of threads in this proc */ + int ret; /* return value for Exec, Fork */ + + char *cmdname; /* ptr to name of thread */ + + int inrendez; + Thread *rendhash; /* Trgrp linked list */ + ulong rendtag; /* rendezvous tag */ + ulong rendval; /* rendezvous value */ + int rendbreak; /* rendezvous has been taken */ + + Chanstate chan; /* which channel operation is current */ + Alt *alt; /* pointer to current alt structure (debugging) */ + + void* udata[NPRIV]; /* User per-thread data pointer */ +}; + +struct Execargs +{ + char *prog; + char **args; + int fd[2]; +}; + +struct Proc +{ + Lock lock; + Label sched; /* for context switches */ + Proc *link; /* in proctab */ + int pid; /* process id */ + int splhi; /* delay notes */ + Thread *thread; /* running thread */ + + int needexec; + Execargs exec; /* exec argument */ + Proc *newproc; /* fork argument */ + char exitstr[ERRMAX]; /* exit status */ + + int rforkflag; + int nthreads; + Tqueue threads; /* All threads of this proc */ + Tqueue ready; /* Runnable threads */ + Lock readylock; + + char printbuf[Printsize]; + int blocked; /* In a rendezvous */ + int pending; /* delayed note pending */ + int nonotes; /* delay notes */ + uint nextID; /* ID of most recently created thread */ + Proc *next; /* linked list of Procs */ + + void *arg; /* passed between shared and unshared stk */ + char str[ERRMAX]; /* used by threadexits to avoid malloc */ + char errbuf[ERRMAX]; /* errstr */ + + void* udata; /* User per-proc data pointer */ +}; + +struct Pqueue { /* Proc queue */ + Lock lock; + Proc *head; + Proc **tail; +}; + +struct Ioproc +{ + int tid; + Channel *c, *creply; + int inuse; + long (*op)(va_list*); + va_list arg; + long ret; + char err[ERRMAX]; + Ioproc *next; +}; + +void _gotolabel(Label*); +int _setlabel(Label*); +void _freeproc(Proc*); +Proc* _newproc(void(*)(void*), void*, uint, char*, int, int); +int _procsplhi(void); +void _procsplx(int); +void _sched(void); +int _schedexec(Execargs*); +void _schedexecwait(void); +void _schedexit(Proc*); +int _schedfork(Proc*); +void _schedinit(void*); +void _systhreadinit(void); +void _threadassert(char*); +void _threadbreakrendez(void); +void __threaddebug(ulong, char*, ...); +#define _threaddebug if(!_threaddebuglevel){}else __threaddebug +void _threadexitsall(char*); +void _threadflagrendez(Thread*); +Proc* _threadgetproc(void); +Proc* _threaddelproc(void); +void _threadsetproc(Proc*); +void _threadinitstack(Thread*, void(*)(void*), void*); +void* _threadmalloc(long, int); +void _threadnote(void*, char*); +void _threadready(Thread*); +ulong _threadrendezvous(ulong, ulong); +void _threadsignal(void); +void _threadsysfatal(char*, va_list); +long _xdec(long*); +void _xinc(long*); +void _threadremove(Proc*, Thread*); + +extern int _threaddebuglevel; +extern char* _threadexitsallstatus; +extern Pqueue _threadpq; +extern Channel* _threadwaitchan; +extern Rgrp _threadrgrp; +extern void _stackfree(void*); + +#define DBGAPPL (1 << 0) +#define DBGSCHED (1 << 16) +#define DBGCHAN (1 << 17) +#define DBGREND (1 << 18) +/* #define DBGKILL (1 << 19) */ +#define DBGNOTE (1 << 20) +#define DBGEXEC (1 << 21) + +#define ioproc_arg(io, type) (va_arg((io)->arg, type)) +extern int _threadgetpid(void); +extern void _threadmemset(void*, int, int); +extern void _threaddebugmemset(void*, int, int); + diff --git a/src/libthread/tprimes b/src/libthread/tprimes Binary files differnew file mode 100755 index 00000000..52d6f05e --- /dev/null +++ b/src/libthread/tprimes diff --git a/src/libthread/tprimes.c b/src/libthread/tprimes.c new file mode 100644 index 00000000..9426bdfa --- /dev/null +++ b/src/libthread/tprimes.c @@ -0,0 +1,62 @@ +#include <lib9.h> +#include <thread.h> + +int quiet; +int goal; +int buffer; +int (*fn)(void(*)(void*), void*, uint) = threadcreate; + +void +primethread(void *arg) +{ + Channel *c, *nc; + int p, i; + + c = arg; + p = recvul(c); + if(p > goal) + threadexitsall(nil); + if(!quiet) + print("%d\n", p); + nc = chancreate(sizeof(ulong), buffer); + (*fn)(primethread, nc, 8192); + for(;;){ + i = recvul(c); + if(i%p) + sendul(nc, i); + } +} + +extern int _threaddebuglevel; + +void +threadmain(int argc, char **argv) +{ + int i; + Channel *c; + + ARGBEGIN{ + case 'D': + _threaddebuglevel = atoi(ARGF()); + break; + case 'q': + quiet = 1; + break; + case 'b': + buffer = atoi(ARGF()); + break; + case 'p': + fn=proccreate; + break; + }ARGEND + + if(argc>0) + goal = atoi(argv[0]); + else + goal = 100; + + c = chancreate(sizeof(ulong), buffer); + (*fn)(primethread, c, 8192); + for(i=2;; i++) + sendul(c, i); +} |