pre-empt both user and kernel, in clock interrupt
usertest.c tests pre-emption kill()
This commit is contained in:
		
							parent
							
								
									5ce9751cab
								
							
						
					
					
						commit
						b548df152b
					
				
					 12 changed files with 152 additions and 47 deletions
				
			
		
							
								
								
									
										30
									
								
								Notes
									
										
									
									
									
								
							
							
						
						
									
										30
									
								
								Notes
									
										
									
									
									
								
							| 
						 | 
					@ -80,16 +80,22 @@ trap() ought to lgdt on return, since currently only done in swtch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
protect hardware interrupt vectors from user INT instructions?
 | 
					protect hardware interrupt vectors from user INT instructions?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
i'm getting a curious interrupt when jumping into user space. maybe
 | 
					 | 
				
			||||||
it's IRQ 0, but it comes at a weird and changing vector (e.g. 119) if
 | 
					 | 
				
			||||||
you don't initialize the PIC. why doesn't jos see this? if i
 | 
					 | 
				
			||||||
initialize the PIC with IRQ_OFFSET 32, the interrupt arrives at vector
 | 
					 | 
				
			||||||
32.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
test out-of-fd cases for creating pipe.
 | 
					test out-of-fd cases for creating pipe.
 | 
				
			||||||
test pipe circular buffer
 | 
					test pipe reader closes then write
 | 
				
			||||||
test pipe writer or reader closes while other active or waiting
 | 
					test two readers, two writers.
 | 
				
			||||||
test exit vs fd reference counts
 | 
					test children being inherited by grandparent &c
 | 
				
			||||||
test write of more than PIPESIZE
 | 
					
 | 
				
			||||||
test reader goes first vs writer goes first
 | 
					kill
 | 
				
			||||||
test streaming of a lot of data
 | 
					  sleep()ing for something
 | 
				
			||||||
 | 
					  running at user level
 | 
				
			||||||
 | 
					  running in kernel
 | 
				
			||||||
 | 
					  ooh, the relevant CPU may never get a clock interrupt
 | 
				
			||||||
 | 
					  should each cpu have its own clock?
 | 
				
			||||||
 | 
					  where to check?
 | 
				
			||||||
 | 
					    loops around sleep()
 | 
				
			||||||
 | 
					    return from any trap
 | 
				
			||||||
 | 
					  rules about being killed deep inside a system call
 | 
				
			||||||
 | 
					  test above cases
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cli/sti in acquire/release should nest!
 | 
				
			||||||
 | 
					  in case you acquire two locks
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								defs.h
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								defs.h
									
										
									
									
									
								
							| 
						 | 
					@ -17,6 +17,8 @@ void swtch(void);
 | 
				
			||||||
void sleep(void *);
 | 
					void sleep(void *);
 | 
				
			||||||
void wakeup(void *);
 | 
					void wakeup(void *);
 | 
				
			||||||
void scheduler(void);
 | 
					void scheduler(void);
 | 
				
			||||||
 | 
					void proc_exit(void);
 | 
				
			||||||
 | 
					void yield(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// swtch.S
 | 
					// swtch.S
 | 
				
			||||||
struct jmpbuf;
 | 
					struct jmpbuf;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								kalloc.c
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								kalloc.c
									
										
									
									
									
								
							| 
						 | 
					@ -158,6 +158,4 @@ ktest()
 | 
				
			||||||
  if(p1 == 0)
 | 
					  if(p1 == 0)
 | 
				
			||||||
    panic("ktest2");
 | 
					    panic("ktest2");
 | 
				
			||||||
  kfree(p1, PAGE * 20);
 | 
					  kfree(p1, PAGE * 20);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  cprintf("ktest ok\n");
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								main.c
									
										
									
									
									
								
							
							
						
						
									
										7
									
								
								main.c
									
										
									
									
									
								
							| 
						 | 
					@ -66,11 +66,12 @@ main()
 | 
				
			||||||
  ide_init(); 
 | 
					  ide_init(); 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // become interruptable
 | 
					  // become interruptable
 | 
				
			||||||
  write_eflags(read_eflags() | FL_IF);
 | 
					  sti();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  p = newproc();
 | 
					  p = newproc();
 | 
				
			||||||
  // load_icode(p, _binary_usertests_start, (unsigned) _binary_usertests_size);
 | 
					  
 | 
				
			||||||
  load_icode(p, _binary_userfs_start, (unsigned) _binary_userfs_size);
 | 
					  load_icode(p, _binary_usertests_start, (unsigned) _binary_usertests_size);
 | 
				
			||||||
 | 
					  //load_icode(p, _binary_userfs_start, (unsigned) _binary_userfs_size);
 | 
				
			||||||
  cprintf("loaded userfs\n");
 | 
					  cprintf("loaded userfs\n");
 | 
				
			||||||
  scheduler();
 | 
					  scheduler();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										42
									
								
								proc.c
									
										
									
									
									
								
							
							
						
						
									
										42
									
								
								proc.c
									
										
									
									
									
								
							| 
						 | 
					@ -184,3 +184,45 @@ wakeup(void *chan)
 | 
				
			||||||
    if(p->state == WAITING && p->chan == chan)
 | 
					    if(p->state == WAITING && p->chan == chan)
 | 
				
			||||||
      p->state = RUNNABLE;
 | 
					      p->state = RUNNABLE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// give up the CPU but stay marked as RUNNABLE
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					yield()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if(curproc[cpu()] == 0 || curproc[cpu()]->state != RUNNING)
 | 
				
			||||||
 | 
					    panic("yield");
 | 
				
			||||||
 | 
					  curproc[cpu()]->state = RUNNABLE;
 | 
				
			||||||
 | 
					  swtch();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					proc_exit()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  struct proc *p;
 | 
				
			||||||
 | 
					  struct proc *cp = curproc[cpu()];
 | 
				
			||||||
 | 
					  int fd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cprintf("exit %x\n", cp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for(fd = 0; fd < NOFILE; fd++){
 | 
				
			||||||
 | 
					    if(cp->fds[fd]){
 | 
				
			||||||
 | 
					      fd_close(cp->fds[fd]);
 | 
				
			||||||
 | 
					      cp->fds[fd] = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cp->state = ZOMBIE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // wake up parent
 | 
				
			||||||
 | 
					  for(p = proc; p < &proc[NPROC]; p++)
 | 
				
			||||||
 | 
					    if(p->pid == cp->ppid)
 | 
				
			||||||
 | 
					      wakeup(p);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // abandon children
 | 
				
			||||||
 | 
					  for(p = proc; p < &proc[NPROC]; p++)
 | 
				
			||||||
 | 
					    if(p->ppid == cp->pid)
 | 
				
			||||||
 | 
					      p->pid = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // switch into scheduler
 | 
				
			||||||
 | 
					  swtch();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								proc.h
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								proc.h
									
										
									
									
									
								
							| 
						 | 
					@ -41,6 +41,7 @@ struct proc{
 | 
				
			||||||
  int pid;
 | 
					  int pid;
 | 
				
			||||||
  int ppid;
 | 
					  int ppid;
 | 
				
			||||||
  void *chan; // sleep
 | 
					  void *chan; // sleep
 | 
				
			||||||
 | 
					  int killed;
 | 
				
			||||||
  struct fd *fds[NOFILE];
 | 
					  struct fd *fds[NOFILE];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  struct Taskstate ts;  // only to give cpu address of kernel stack
 | 
					  struct Taskstate ts;  // only to give cpu address of kernel stack
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,7 @@ acquire_spinlock(uint32_t* lock)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // on a real machine there would be a memory barrier here
 | 
					  // on a real machine there would be a memory barrier here
 | 
				
			||||||
  if(DEBUG) cprintf("cpu%d: acquiring at %x\n", cpu_id, getcallerpc(&lock));
 | 
					  if(DEBUG) cprintf("cpu%d: acquiring at %x\n", cpu_id, getcallerpc(&lock));
 | 
				
			||||||
  write_eflags(read_eflags() & ~FL_IF);
 | 
					  cli();
 | 
				
			||||||
  if (*lock == cpu_id)
 | 
					  if (*lock == cpu_id)
 | 
				
			||||||
    panic("recursive lock");
 | 
					    panic("recursive lock");
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,7 @@ release_spinlock(uint32_t* lock)
 | 
				
			||||||
    panic("release_spinlock: releasing a lock that i don't own\n");
 | 
					    panic("release_spinlock: releasing a lock that i don't own\n");
 | 
				
			||||||
  *lock = LOCK_FREE;
 | 
					  *lock = LOCK_FREE;
 | 
				
			||||||
  // on a real machine there would be a memory barrier here
 | 
					  // on a real machine there would be a memory barrier here
 | 
				
			||||||
  write_eflags(read_eflags() | FL_IF);
 | 
					  sti();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										48
									
								
								syscall.c
									
										
									
									
									
								
							
							
						
						
									
										48
									
								
								syscall.c
									
										
									
									
									
								
							| 
						 | 
					@ -155,32 +155,7 @@ sys_fork()
 | 
				
			||||||
int
 | 
					int
 | 
				
			||||||
sys_exit()
 | 
					sys_exit()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  struct proc *p;
 | 
					  proc_exit();
 | 
				
			||||||
  struct proc *cp = curproc[cpu()];
 | 
					 | 
				
			||||||
  int fd;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for(fd = 0; fd < NOFILE; fd++){
 | 
					 | 
				
			||||||
    if(cp->fds[fd]){
 | 
					 | 
				
			||||||
      fd_close(cp->fds[fd]);
 | 
					 | 
				
			||||||
      cp->fds[fd] = 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  cp->state = ZOMBIE;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // wake up parent
 | 
					 | 
				
			||||||
  for(p = proc; p < &proc[NPROC]; p++)
 | 
					 | 
				
			||||||
    if(p->pid == cp->ppid)
 | 
					 | 
				
			||||||
      wakeup(p);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // abandon children
 | 
					 | 
				
			||||||
  for(p = proc; p < &proc[NPROC]; p++)
 | 
					 | 
				
			||||||
    if(p->ppid == cp->pid)
 | 
					 | 
				
			||||||
      p->pid = 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // switch into scheduler
 | 
					 | 
				
			||||||
  swtch();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return 0;
 | 
					  return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -250,6 +225,24 @@ sys_block(void)
 | 
				
			||||||
  return 0;
 | 
					  return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int
 | 
				
			||||||
 | 
					sys_kill()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  int pid;
 | 
				
			||||||
 | 
					  struct proc *p;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fetcharg(0, &pid);
 | 
				
			||||||
 | 
					  for(p = proc; p < &proc[NPROC]; p++){
 | 
				
			||||||
 | 
					    if(p->pid == pid && p->state != UNUSED){
 | 
				
			||||||
 | 
					      p->killed = 1;
 | 
				
			||||||
 | 
					      if(p->state == WAITING)
 | 
				
			||||||
 | 
					        p->state = RUNNABLE;
 | 
				
			||||||
 | 
					      return 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
syscall()
 | 
					syscall()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -286,6 +279,9 @@ syscall()
 | 
				
			||||||
  case SYS_block:
 | 
					  case SYS_block:
 | 
				
			||||||
    ret = sys_block();
 | 
					    ret = sys_block();
 | 
				
			||||||
    break;
 | 
					    break;
 | 
				
			||||||
 | 
					  case SYS_kill:
 | 
				
			||||||
 | 
					    ret = sys_kill();
 | 
				
			||||||
 | 
					    break;
 | 
				
			||||||
  default:
 | 
					  default:
 | 
				
			||||||
    cprintf("unknown sys call %d\n", num);
 | 
					    cprintf("unknown sys call %d\n", num);
 | 
				
			||||||
    // XXX fault
 | 
					    // XXX fault
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,3 +7,4 @@
 | 
				
			||||||
#define SYS_read 7
 | 
					#define SYS_read 7
 | 
				
			||||||
#define SYS_close 8
 | 
					#define SYS_close 8
 | 
				
			||||||
#define SYS_block 9
 | 
					#define SYS_block 9
 | 
				
			||||||
 | 
					#define SYS_kill 10
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								trap.c
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								trap.c
									
										
									
									
									
								
							| 
						 | 
					@ -45,6 +45,8 @@ trap(struct Trapframe *tf)
 | 
				
			||||||
    struct proc *cp = curproc[cpu()];
 | 
					    struct proc *cp = curproc[cpu()];
 | 
				
			||||||
    if(cp == 0)
 | 
					    if(cp == 0)
 | 
				
			||||||
      panic("syscall with no proc");
 | 
					      panic("syscall with no proc");
 | 
				
			||||||
 | 
					    if(cp->killed)
 | 
				
			||||||
 | 
					      proc_exit();
 | 
				
			||||||
    cp->tf = tf;
 | 
					    cp->tf = tf;
 | 
				
			||||||
    syscall();
 | 
					    syscall();
 | 
				
			||||||
    if(cp != curproc[cpu()])
 | 
					    if(cp != curproc[cpu()])
 | 
				
			||||||
| 
						 | 
					@ -55,11 +57,20 @@ trap(struct Trapframe *tf)
 | 
				
			||||||
      panic("trap ret wrong tf");
 | 
					      panic("trap ret wrong tf");
 | 
				
			||||||
    if(read_esp() < (unsigned)cp->kstack || read_esp() >= (unsigned)cp->kstack + KSTACKSIZE)
 | 
					    if(read_esp() < (unsigned)cp->kstack || read_esp() >= (unsigned)cp->kstack + KSTACKSIZE)
 | 
				
			||||||
      panic("trap ret esp wrong");
 | 
					      panic("trap ret esp wrong");
 | 
				
			||||||
 | 
					    if(cp->killed)
 | 
				
			||||||
 | 
					      proc_exit();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if(v == (IRQ_OFFSET + IRQ_TIMER)){
 | 
					  if(v == (IRQ_OFFSET + IRQ_TIMER)){
 | 
				
			||||||
 | 
					    struct proc *cp = curproc[cpu()];
 | 
				
			||||||
    lapic_timerintr();
 | 
					    lapic_timerintr();
 | 
				
			||||||
 | 
					    if(cp){
 | 
				
			||||||
 | 
					      sti();
 | 
				
			||||||
 | 
					      if(cp->killed)
 | 
				
			||||||
 | 
					        proc_exit();
 | 
				
			||||||
 | 
					      yield();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if(v == (IRQ_OFFSET + IRQ_IDE)){
 | 
					  if(v == (IRQ_OFFSET + IRQ_IDE)){
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										49
									
								
								usertests.c
									
										
									
									
									
								
							
							
						
						
									
										49
									
								
								usertests.c
									
										
									
									
									
								
							| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
// simple fork and pipe read/write
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
char buf[2048];
 | 
					char buf[2048];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// simple fork and pipe read/write
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
pipe1()
 | 
					pipe1()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -47,9 +47,54 @@ pipe1()
 | 
				
			||||||
  puts("pipe1 ok\n");
 | 
					  puts("pipe1 ok\n");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// meant to be run w/ at most two CPUs
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					preempt()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  int pid1, pid2, pid3;
 | 
				
			||||||
 | 
					  int pfds[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pid1 = fork();
 | 
				
			||||||
 | 
					  if(pid1 == 0)
 | 
				
			||||||
 | 
					    while(1)
 | 
				
			||||||
 | 
					      ;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  pid2 = fork();
 | 
				
			||||||
 | 
					  if(pid2 == 0)
 | 
				
			||||||
 | 
					    while(1)
 | 
				
			||||||
 | 
					      ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pipe(pfds);
 | 
				
			||||||
 | 
					  pid3 = fork();
 | 
				
			||||||
 | 
					  if(pid3 == 0){
 | 
				
			||||||
 | 
					    close(pfds[0]);
 | 
				
			||||||
 | 
					    if(write(pfds[1], "x", 1) != 1)
 | 
				
			||||||
 | 
					      puts("preempt write error");
 | 
				
			||||||
 | 
					    close(pfds[1]);
 | 
				
			||||||
 | 
					    while(1)
 | 
				
			||||||
 | 
					      ;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  close(pfds[1]);
 | 
				
			||||||
 | 
					  if(read(pfds[0], buf, sizeof(buf)) != 1){
 | 
				
			||||||
 | 
					    puts("preempt read error");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  close(pfds[0]);
 | 
				
			||||||
 | 
					  kill(pid1);
 | 
				
			||||||
 | 
					  kill(pid2);
 | 
				
			||||||
 | 
					  kill(pid3);
 | 
				
			||||||
 | 
					  wait();
 | 
				
			||||||
 | 
					  wait();
 | 
				
			||||||
 | 
					  wait();
 | 
				
			||||||
 | 
					  puts("preempt ok\n");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
main()
 | 
					main()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					  puts("usertests starting\n");
 | 
				
			||||||
  pipe1();
 | 
					  pipe1();
 | 
				
			||||||
 | 
					  //preempt();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  while(1)
 | 
					  while(1)
 | 
				
			||||||
    ;
 | 
					    ;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								usys.S
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								usys.S
									
										
									
									
									
								
							| 
						 | 
					@ -10,9 +10,11 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
STUB(fork)
 | 
					STUB(fork)
 | 
				
			||||||
STUB(exit)
 | 
					STUB(exit)
 | 
				
			||||||
 | 
					STUB(wait)
 | 
				
			||||||
STUB(cons_putc)
 | 
					STUB(cons_putc)
 | 
				
			||||||
STUB(pipe)
 | 
					STUB(pipe)
 | 
				
			||||||
STUB(read)
 | 
					STUB(read)
 | 
				
			||||||
STUB(write)
 | 
					STUB(write)
 | 
				
			||||||
STUB(close)
 | 
					STUB(close)
 | 
				
			||||||
STUB(block)
 | 
					STUB(block)
 | 
				
			||||||
 | 
					STUB(kill)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue