Ich muss eine Benutzer-Level-Thread-Bibliothek als eine Hausaufgabe mit setjmp/longjmp implementieren. Dies ist der Code, den ich schrieb:setjmp longjmp mit Stack
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/select.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <setjmp.h>
#define STACKSIZE 128*1024 //approriate stack size
typedef void (*ult_func)();
struct tcb_str; //forward declaration
typedef struct tcb_str {
//fill this struct with statusinformations
stack_t stack; //stack for local vars
jmp_buf buf;
int id;
void (*threadfunc)();
int waitingfor; //ID of Thread/ Filediscriptor the Thread is currently waiting for
int waitingtype; //0 = Thread, 1 = fd
int exitnumber; //Is set on exit
} tcb;
typedef struct queue queue;
struct queue *runqueue;
struct queue *blockedqueue;
struct queue *zombiequeue;
jmp_buf backtonormal;
int counter = 0;
struct queue {
struct queue_node *start;
struct queue_node *end;
};
struct queue_node {
struct tcb_str *element;
struct queue_node *next;
};
struct queue_node* pop(struct queue *qu) {
if (qu == NULL || qu->start == NULL) {
return NULL;
}
struct queue_node *node = qu->start;
qu->start = node->next;
if (qu->start == NULL) {
qu->end = NULL;
}
node->next = NULL;
return node;
}
int push(struct queue *qu, struct queue_node *node) {
if (qu == NULL) {
return -1;
}
node->next = NULL;
if (qu->end == NULL) {
qu->start = qu->end = node;
} else {
qu->end->next = node;
qu->end = node;
}
return 1;
}
struct queue_node* removeByTid(struct queue *qu, int tid) {
struct queue_node* tmp = qu->start;
struct queue_node* previous = qu->start;
if(tmp->element->id == tid) {
pop(qu);
return tmp;
}
do {
if(tmp->element->id == tid) {
//What if first and only
previous->next = tmp->next;
//What if only one left after
tmp->next = NULL;
if(qu->start->next == NULL) {
qu->end = qu->start;
}
return tmp;
}
previous = tmp;
}
while((tmp = tmp->next));
return NULL;
}
struct queue *initqueue() {
queue *qu = malloc(sizeof(*qu));
if (qu == NULL) {
return NULL;
}
qu->start = qu->end = NULL;
return qu;
}
int checkfordata(int fd) {
int data; //returns != 0 if data is available
struct timeval tv_str;
fd_set fds;
FD_ZERO(&fds);
if (!FD_ISSET(fd, &fds)) {
FD_SET(fd, &fds); //Creating fd_set for select()
}
tv_str.tv_sec = 0;
tv_str.tv_usec = 0;
//is data available?
data = select(fd + 1, &fds, NULL, NULL, &tv_str);
FD_CLR(fd, &fds);
return data;
}
void schedulerThread() {
while(1) {
//Check blocked Threads
struct queue_node* tmp = blockedqueue->start;
fd_set fds;
FD_ZERO(&fds);
if(tmp != NULL) {
//Go through blocked Threads
do {
int data = checkfordata(tmp->element->waitingfor);
if(data > 0) {
removeByTid(blockedqueue, tmp->element->id);
//Add to running queue (at start)
tmp->next = runqueue->start;
runqueue->start = tmp;
return;
}
else {
FD_SET(tmp->element->waitingfor, &fds);
}
}
while((tmp = tmp->next));
}
if(runqueue->start == NULL) {
if(blockedqueue->start == NULL) {
free(runqueue);
free(blockedqueue);
struct queue_node* qu;
while((qu = pop(zombiequeue)) != NULL) {
free(qu->element->stack.ss_sp);
free(qu);
}
free(zombiequeue);
return;
}
else {
struct timeval tv_str;
tv_str.tv_sec = 0;
tv_str.tv_usec = 800 * 1000;
//We have to copy fds, as select will mess it up
fd_set fdset = fds;
select(FD_SETSIZE, &fdset, NULL, NULL, &tv_str);
}
}
else {
return;
}
}
}
/*
This function only exists to tell the process to use an empty stack for the thread
*/
void signalHandlerSpawn(int arg) {
if (setjmp(runqueue->start->element->buf)) {
runqueue->start->element->threadfunc();
longjmp(backtonormal, 1);
}
return;
}
int ult_spawn(ult_func f) {
struct tcb_str* tcb = malloc(sizeof(struct tcb_str));
tcb->threadfunc = f;
tcb->waitingfor = -1;
tcb->waitingtype = -1;
tcb->id = ++counter;
tcb->stack.ss_flags = 0;
tcb->stack.ss_size = STACKSIZE;
tcb->stack.ss_sp = malloc(STACKSIZE);
if (tcb->stack.ss_sp == 0) {
perror("Could not allocate stack.");
exit(1);
}
stack_t oldStack;
sigaltstack(&(tcb->stack), 0);
struct sigaction sa;
struct sigaction oldHandler;
sa.sa_handler = &signalHandlerSpawn;
sa.sa_flags = SA_ONSTACK;
sigemptyset(&sa.sa_mask);
sigaction(SIGUSR1, &sa, &oldHandler);
struct queue_node* node = malloc(sizeof(struct queue_node));
node->element = tcb;
push(runqueue, node);
struct queue_node* q = runqueue->start;
runqueue->start = runqueue->end;
raise(SIGUSR1);
/* Restore the original stack and handler */
sigaltstack(&oldStack, 0);
sigaction(SIGUSR1, &oldHandler, 0);
runqueue->start = q;
return tcb->id;
}
void ult_yield() {
if(runqueue->start == NULL) {
exit(1);
//TODO clean up
}
//We're the only one, so no need to schedule
if(runqueue->start == runqueue->end && blockedqueue->start == NULL && runqueue->start != NULL) {
return;
}
else {
if (setjmp(runqueue->start->element->buf))
return;
else {
struct queue_node* tmp = pop(runqueue);
push(runqueue, tmp);
longjmp(backtonormal, 1);
}
}
}
int ult_read(int fd, void *buf, int count) {
if (setjmp(runqueue->start->element->buf)) {
return read(fd, buf, count);
}
else {
struct queue_node* tmp = pop(runqueue);
tmp->element->waitingfor = fd;
tmp->element->waitingtype = 1;
push(blockedqueue, tmp);
longjmp(backtonormal, 1);
}
return -1;
}
void ult_init(ult_func f) {
runqueue = initqueue();
blockedqueue = initqueue();
zombiequeue = initqueue();
ult_spawn(f);
while(1) {
if(setjmp(backtonormal))
continue;
else {
schedulerThread();
if(runqueue->start == NULL)
return; //TODO clean up
longjmp(runqueue->start->element->buf, 1);
}
}
}
void threadA()
{
int fd;
char *inpt = "/dev/random";
char buf[8];
fd = open(inpt, O_RDONLY, O_NONBLOCK);
if (fd == -1)
{
perror("open()");
}
while(1)
{
memset(buf, 0, 8);
ult_read(fd, &buf, sizeof(buf));
}
}
void threadB()
{
char input[512] = {0};
while(1)
{
memset(input, 0, 512);
ult_read(STDIN_FILENO, &input, 512);
if(strcmp(input, "stats\n") == 0)
{
//print stats
continue;
}
}
}
void myInit()
{
int status;
ult_spawn(&threadA);
ult_spawn(&threadB);
while(1) {
ult_yield();
}
}
int main() {
ult_init(&myInit);
return 0;
}
Das Problem tritt in Ult_read auf. Sobald diese Funktion aufgerufen wird, springt sie zuerst zum Scheduler zurück. Der Scheduler prüft, ob Daten verfügbar sind (um zu verhindern, dass der gesamte Prozess blockiert), und springt zu der Funktion zurück, sobald Daten zu lesen sind. Jetzt, wenn die Funktion zurückkehrt, erhalte ich einen Segmentierungsfehler. Valgrind sagt mir:
Jump to the invalid address stated on the next line
==20408== at 0x0: ???
==20408== Address 0x0 is not stack'd, malloc'd or (recently) free'd
Ult_yield funktioniert gut, obwohl es die gleiche Technik verwendet. Ich habe diese Frage SetJmp/LongJmp: Why is this throwing a segfault? überprüft, aber ich denke, das ist ein anderes Problem, da ich einen separaten Stapel für jeden "Thread" erstellen. Kann mir jemand erklären, was das Problem ist und wie man es beheben kann?
Hinweis: Es gibt auch sigsetjmp(). Und ich denke nicht, dass Sie in Signal-Handler hinein-/herausspringen sollten (es sei denn, Sie wissen, was Sie tun, in welchem Fall es implementierungsabhängig ist). – wildplasser
Mein Code basiert auf diesem Artikel: http: //www.evanjones. ca/software/threading.html Der Autor verwendet den gleichen Ansatz im Kapitel "Implementieren von Fasern mit longjmp" – Simon