970 likes | 1.11k Views
I P C u920938 顏嘉佑. Chapter 1: System V. System V IPC. Semaphore: syncronization Message Queue: one to one share data Share Memory: like bulletin board . System Call. Chapter 2: Semaphore. What is semaphore ?. 2.1: introduction.
E N D
System V IPC • Semaphore: syncronization • Message Queue: one to one share data • Share Memory: like bulletin board System Call
2.1: introduction Critical Section: 受保護的資源,被多個process所共享,但同一時間,只能有一個process使用之。 section 1a section 2a Wait(S) Wait(S) Share Data Critical Section Crtical Section Signal(s) Signal(S) section 1b section 2b
not efficiency!!! Singal(S) { S++; } Wait(S) { while(S<=0) ; S--; } How to improve???
System V Semaphore VSKernel Syncronization • 每個IPC semaphore資源提供多組計數器 (semaphore primitive),可對多個資源進行保護動作。 • 提供UNDO機制,使當process意外終止時,所掌控的semaphore得以復原,避免dead lock的產生。
2.2: Data Structure • ipc_ids • sem_array • sem_queue • sem • sembuf • sem_undo
structipc_ids{ • int in_use; • int max_id; • unsignedshort seq; • unsignedshort seq_max; • structmutex mutex; • structipc_id_ary nullentry; • structipc_id_ary* entries;}; staticstructipc_idssem_ids;
struct sem_array{ • struct kern_ipc_perm sem_perm; • int sem_id; • time_t sem_otime; • time_t sem_ctime; • struct sem *sem_base; • struct sem_queue *sem_pending; • structsem_queue **sem_pending_last; • struct sem_undo *undo; • unsigned long sem_nsems; };
structsem_queue{ • structsem_queue * next; • structsem_queue ** prev; • structtask_struct* sleeper; • structsem_undo * undo; • int pid; • int status; • structsem_array * sma; • intid; • structsembuf * sops; • int nsops; • int alter; }
structsem{ • int semval; • int sempid; };
structsembuf{ • unsigned short sem_num; • short sem_op; • short sem_flg; };
structsem_undo{ • structsem_undo * proc_next; • structsem_undo * id_next; • int semid; • short * semadj; };
sem_ids的結構圖 O'Reilly-Understanding_The_Linux Kernel_3rd
2.3 重要的函式 • sys_semget • newary、sem_buildid • sys_semctl • semctl_nolock、semctl_main、semctl_down • sys_semop • try_atomic_semop、update_queue • sem_exit
sys_semget • FLAG : 依使用者需求做調整 • IPC_CREAT • IPC_EXCL • CHECK參數是否有誤? • 根據FLAG判斷是否是建立新的IPC物件還是 回傳已存在的identifier,甚至回傳錯誤訊息。
sys_semget: flow chart true false
asmlinkage long sys_semget (key_t key, int nsems, int semflg) { int id, err = -EINVAL; struct sem_array *sma; struct ipc_namespace *ns; ns = current->nsproxy->ipc_ns; if (nsems < 0 || nsems > ns->sc_semmsl) return -EINVAL; mutex_lock(&sem_ids(ns).mutex); // kernel syncronization if (key == IPC_PRIVATE) { err = newary(ns, key, nsems, semflg); } else if ((id = ipc_findkey(&sem_ids(ns), key)) == -1) { check range create a new IPC key exist in the system ?
If not found the key and not set the ENOENT flag if (!(semflg & IPC_CREAT)) err = -ENOENT; else err = newary(ns, key, nsems, semflg); } else if (semflg & IPC_CREAT && semflg & IPC_EXCL) { err = -EEXIST; } else { sma = sem_lock(ns, id); BUG_ON(sma==NULL); if (nsems > sma->sem_nsems) err = -EINVAL; else if (ipcperms(&sma->sem_perm, semflg)) err = -EACCES; else { int semid = sem_buildid(ns, id, sma->sem_perm.seq); err = security_sem_associate(sma, semflg); if (!err) err = semid; IPC_EXCL IPC_CREAT are both set so return EEXIST S*IPCMNI+id
} sem_unlock(sma); } sem_unlock(sma); } mutex_unlock(&sem_ids(ns).mutex); return err; }
newary: flow chart failure size=sizeof (*sma) + nsems * sizeof (struct sem); ipc_rcu_alloc(size); ENOMEM id = ipc_addid(&sem_ids(ns), &sma-> sem_perm, ns->sc_semmni); Ipc_rcu_putref(sma); return -ENOSPC; Id == -1 Id !=-1 set initial value sem_buildid : s+IPCMNI+id DONE
staticintnewary (struct ipc_namespace *ns, key_t key, int nsems, int semflg) ………. if (!nsems) return -EINVAL; if (ns->used_sems + nsems > ns->sc_semmns) return -ENOSPC; size = sizeof (*sma) + nsems * sizeof (struct sem); sma = ipc_rcu_alloc(size); if (!sma) { return -ENOMEM; } memset (sma, 0, size); sma->sem_perm.mode = (semflg & S_IRWXUGO); sma->sem_perm.key = key; sma->sem_perm.security = NULL; retval = security_sem_alloc(sma); if (retval) { ipc_rcu_putref(sma); return retval; }
id = ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni); if(id == -1) { security_sem_free(sma); ipc_rcu_putref(sma); return -ENOSPC; } ns->used_sems += nsems; sma->sem_id = sem_buildid(ns, id, sma->sem_perm.seq); sma->sem_base = (struct sem *) &sma[1]; /* sma->sem_pending = NULL; */ sma->sem_pending_last = &sma->sem_pending; /* sma->undo = NULL; */ sma->sem_nsems = nsems; sma->sem_ctime = get_seconds(); sem_unlock(sma); return sma->sem_id; } for (id = 0; id < size; id++) { if(ids->entries->p[id] == NULL) goto found; } …………….. ids->entries->p[id] = new; return id;
sys_semctl (int semid, int semnum, int cmd, union semun arg) • 判斷傳入的cmd類型。 • 若為IPC_INFO ,SEM_INFO,SEM_STAT這三類的話,執行semctl_nolock函式。 • 若為GETVAL,GETALL,GETPID,GETNCNT,GETZCNT,IPC_STAT,SETVAL,SETALL。執行semctl_main函式。 • 若為IPC_RMID,IPC_SET則執行semctl_down函式。
sys_semctl: flow chart true cmd = IPC_INFO? cmd = SEM_INFO? cmd = SEM_STAT? semct_nolock false Cmd = GETALL? Cmd = GETVAL? Cmd = GETPID? Cmd = GETNCNT? Cmd = GETZVNT? Cmd = IPC_STAT? Cmd = SETVAL? Cmd = SETALL? true semctl_main false true cmd = IPC_RMID? cmd = IPC_SET? Semctl_down false EINVAL
switch(cmd) { case IPC_INFO: case SEM_INFO: case SEM_STAT: err = semctl_nolock(ns,semid,semnum,cmd,version,arg); return err; case GETALL: case GETVAL: case GETPID: case GETNCNT: case GETZCNT: case IPC_STAT: case SETVAL: case SETALL: err = semctl_main(ns,semid,semnum,cmd,version,arg); return err; case IPC_RMID: case IPC_SET: mutex_lock(&sem_ids(ns).mutex); err = semctl_down(ns,semid,semnum,cmd,version,arg); mutex_unlock(&sem_ids(ns).mutex); return err; default: return -EINVAL; }
semctl_nolock: flow chart true fill seminfo items cmd= IPC_INFO or SEM_INFO? false EINVAL Cmd=SEM_STAT? copy_to_user true DONE Get semid64_ds info copy_semid_to_user DONE
struct seminfo seminfo; ………. memset(&seminfo,0,sizeof(seminfo)); seminfo.semmni = ns->sc_semmni; seminfo.semmns = ns->sc_semmns; seminfo.semmsl = ns->sc_semmsl; seminfo.semopm = ns->sc_semopm; seminfo.semvmx = SEMVMX; seminfo.semmnu = SEMMNU; seminfo.semmap = SEMMAP; seminfo.semume = SEMUME; mutex_lock(&sem_ids(ns).mutex); if (cmd == SEM_INFO) { seminfo.semusz = sem_ids(ns).in_use; seminfo.semaem = ns->used_sems; } else { seminfo.semusz = SEMUSZ; seminfo.semaem = SEMAEM; } max_id = sem_ids(ns).max_id;
struct semid64_ds tbuf; int id; if(semid >= sem_ids(ns).entries->size) return -EINVAL; memset(&tbuf,0,sizeof(tbuf)); sma = sem_lock(ns, semid); if(sma == NULL) return -EINVAL; err = -EACCES; if (ipcperms (&sma->sem_perm, S_IRUGO)) goto out_unlock; err = security_sem_semctl(sma, cmd); if (err) goto out_unlock; id = sem_buildid(ns, semid, sma->sem_perm.seq); kernel_to_ipc64_perm(&sma->sem_perm, &tbuf.sem_perm); tbuf.sem_otime = sma->sem_otime; tbuf.sem_ctime = sma->sem_ctime; tbuf.sem_nsems = sma->sem_nsems; sem_unlock(sma); if (copy_semid_to_user (arg.buf, &tbuf, version)) return -EFAULT; return id; 35768*seq + id
EINVAL semctl_main: flow chart false cmd = SETVAL? failure sma = sem_lock() false EINVAL failure cmd = GETZCNT? OK count_semzcnt() EIDRM sem_checkid false OK failure count_semncnt() cmd = GETTNCNT? ipcperms EACCESS false OK true cmd = GRTPID? cmd = GETALL? get sem_base false false true cmd = SETALL? cmd = GETVAL set sem_base false false cmd = IPC_STAT? semnum < 0 || semnum >= nsems true true Get semid64_ds EINVAL
…………………… sma = sem_lock(ns, semid); if(sma==NULL) return -EINVAL; nsems = sma->sem_nsems; err=-EIDRM; if (sem_checkid(ns,sma,semid)) goto out_unlock; err = -EACCES; if (ipcperms (&sma->sem_perm, (cmd==SETVAL||cmd==SETALL)?S_IWUGO\ :S_IRUGO)) goto out_unlock; err = security_sem_semctl(sma, cmd); if (err) goto out_unlock;
GETALL: ushort* sem_io = fast_sem_io; ……………….. for (i = 0; i < sma->sem_nsems; i++) sem_io[i] = sma->sem_base[i].semval; sem_unlock(sma); err = 0; if(copy_to_user(array, sem_io, nsems*sizeof(ushort))) err = -EFAULT; goto out_free;
SETALL: if (copy_from_user (sem_io, arg.array,\ nsems*sizeof(ushort))) { ipc_lock_by_ptr(&sma->sem_perm); ipc_rcu_putref(sma); sem_unlock(sma); err = -EFAULT; goto out_free; } for (i = 0; i < nsems; i++) { if (sem_io[i] > SEMVMX) { …… sem_unlock(sma); err = -ERANGE; goto out_free; } } for (i = 0; i < nsems; i++) sma->sem_base[i].semval = sem_io[i]; for (un = sma->undo; un; un = un->id_next) for (i = 0; i < nsems; i++) un->semadj[i] = 0; sma->sem_ctime = get_seconds(); update_queue(sma); err = 0; goto out_unlock;
IPC_STAT GETVAL GETPID case IPC_STAT:{ struct semid64_ds tbuf; memset(&tbuf,0,sizeof(tbuf)); kernel_to_ipc64_perm(&sma->sem_perm, &tbuf.sem_perm); tbuf.sem_otime = sma->sem_otime; tbuf.sem_ctime = sma->sem_ctime; tbuf.sem_nsems = sma->sem_nsems; sem_unlock(sma); if (copy_semid_to_user (arg.buf, &tbuf, version)) return -EFAULT; return0; } -------------------------------------------------------------------------------------- case GETVAL: err = curr->semval; goto out_unlock; case GETPID: err = curr->sempid; goto out_unlock; return err;
count_semzcnt staticint count_semzcnt (struct sem_array * sma, ushort semnum) { int semzcnt; struct sem_queue * q; semzcnt = 0; for (q = sma->sem_pending; q; q = q->next) { struct sembuf * sops = q->sops; int nsops = q->nsops; int i; for (i = 0; i < nsops; i++) if (sops[i].sem_num == semnum && (sops[i].sem_op == 0) && !(sops[i].sem_flg & IPC_NOWAIT)) semzcnt++; } return semzcnt; }
count_semncnt staticint count_semncnt (struct sem_array * sma, ushort semnum) { int semncnt; struct sem_queue * q; semncnt = 0; for (q = sma->sem_pending; q; q = q->next) { struct sembuf * sops = q->sops; int nsops = q->nsops; int i; for (i = 0; i < nsops; i++) if (sops[i].sem_num == semnum && (sops[i].sem_op < 0) && !(sops[i].sem_flg & IPC_NOWAIT)) semncnt++; } return semncnt; }
semctl_down: flow chart failure true cmd = IPC_SET? copy_semid_from_user EFAULT false OK failure sma = sem_lock() EINVAL OK failure OK failure EPERM EIDRM check cuid uid ? sem_checkid OK true END cmd = IPC_RMID? cmd = IPC_SET? false true cmd = IPC_SET? cmd = IPC_SET? END false EINVAL
if(cmd == IPC_SET) { if(copy_semid_from_user (&setbuf, arg.buf, version)) return -EFAULT; } sma = sem_lock(ns, semid); if(sma==NULL) return -EINVAL; if (sem_checkid(ns,sma,semid)) { err=-EIDRM; goto out_unlock; } …………………………………. case IPC_RMID: freeary(ns, sma, semid); err = 0; break; case IPC_SET: ipcp->uid = setbuf.uid; ipcp->gid = setbuf.gid; ipcp->mode = (ipcp->mode & ~S_IRWXUGO) | (setbuf.mode & S_IRWXUGO); sma->sem_ctime = get_seconds(); sem_unlock(sma); err = 0; break;
freeary for (un = sma->undo; un; un = un->id_next) un->semid = -1; /* Wake up all pending processes and let them fail with EIDRM. */ q = sma->sem_pending; while(q) { struct sem_queue *n; /* lazy remove_from_queue: we are killing the whole queue */ q->prev = NULL; n = q->next; q->status = IN_WAKEUP; wake_up_process(q->sleeper); /* doesn't sleep */ smp_wmb(); q->status = -EIDRM; /* hands-off q */ q = n; } /* Remove the semaphore set from the ID array*/ sma = sem_rmid(ns, id); sem_unlock(sma); ids->in_use--; if (lid == ids->max_id) { do { lid--; if(lid == -1) break; } while (ids->entries->p[lid] == NULL); ids->max_id = lid; }
sys_semop if (nsops < 1 || semid < 0) return -EINVAL; if (nsops > ns->sc_semopm) return -E2BIG; if(nsops > SEMOPM_FAST) { sops = kmalloc(sizeof(*sops)*nsops,GFP_KERNEL); if(sops==NULL) return -ENOMEM; } if (copy_from_user (sops, tsops, nsops * sizeof(*tsops))) { error=-EFAULT; goto out_free; } ………… max = 0;
for (sop = sops; sop < sops + nsops; sop++) { if (sop->sem_num >= max) max = sop->sem_num; if (sop->sem_flg & SEM_UNDO) undos = 1; if (sop->sem_op != 0) alter = 1; } error = try_atomic_semop (sma, sops, nsops, un, current->tgid); if (error <= 0) { if (alter && error == 0) update_queue (sma); goto out_unlock_free; } queue.sma = sma; queue.sops = sops; queue.nsops = nsops; queue.undo = un; queue.pid = current->tgid; queue.id = semid; queue.alter = alter;
if (alter) append_to_queue(sma ,&queue); else prepend_to_queue(sma ,&queue); queue.status = -EINTR; queue.sleeper = current; current->state = TASK_INTERRUPTIBLE; sem_unlock(sma); …………… schedule();
staticvoid update_queue (struct sem_array * sma) { int error; struct sem_queue * q; q = sma->sem_pending; while(q) { error = try_atomic_semop(sma, q->sops, q->nsops, q->undo, q->pid); /* Does q->sleeper still need to sleep? */ if (error <= 0) { struct sem_queue *n; remove_from_queue(sma,q); q->status = IN_WAKEUP; if (q->alter) n = sma->sem_pending; else n = q->next; wake_up_process(q->sleeper); smp_wmb(); q->status = error; q = n; } else { q = q->next; } } } update queue
for (sop = sops; sop < sops + nsops; sop++) { curr = sma->sem_base + sop->sem_num; sem_op = sop->sem_op; result = curr->semval; if (!sem_op && result) goto would_block; result += sem_op; if (result < 0) goto would_block; if (result > SEMVMX) goto out_of_range; if (sop->sem_flg & SEM_UNDO) { int undo = un->semadj[sop->sem_num] - sem_op; /* * Exceeding the undo range is an error. */ if (undo < (-SEMAEM - 1) || undo > SEMAEM) goto out_of_range; } curr->semval = result; } try_atomic_semop
sop--; while (sop >= sops) { sma->sem_base[sop->sem_num].sempid = pid; if (sop->sem_flg & SEM_UNDO) un->semadj[sop->sem_num] -= sop->sem_op; sop--; } sma->sem_otime = get_seconds(); return0;
out_of_range: result = -ERANGE; goto undo; would_block: if (sop->sem_flg & IPC_NOWAIT) result = -EAGAIN; else result = 1; undo: sop--; while (sop >= sops) { sma->sem_base[sop->sem_num].semval -= sop->sem_op; sop--; } return result;
for (up = &undo_list->proc_list; (u = *up); *up = u->proc_next, kfree(u)) { struct sem_array *sma; int nsems, i; struct sem_undo *un, **unp; int semid; semid = u->semid; if(semid == -1) continue; sma = sem_lock(ns, semid); if (sma == NULL) continue; if (u->semid == -1) goto next_entry; BUG_ON(sem_checkid(ns,sma,u->semid)); for (unp = &sma->undo; (un = *unp); unp = &un->id_next) { if (u == un) goto found; } printk ("exit_sem undo list error id=%d\n", u->semid); goto next_entry; sem_exit