📄 perfmon.c
字号:
/* * Allocates the sampling buffer and remaps it into caller's address space */static intpfm_smpl_buffer_alloc(pfm_context_t *ctx, unsigned long which_pmds, unsigned long entries, void **user_addr){ struct mm_struct *mm = current->mm; struct vm_area_struct *vma; unsigned long addr, size, regcount; void *smpl_buf; pfm_smpl_buffer_desc_t *psb; regcount = pfm_smpl_entry_size(&which_pmds, 1); /* note that regcount might be 0, in this case only the header for each * entry will be recorded. */ /* * 1 buffer hdr and for each entry a header + regcount PMDs to save */ size = PAGE_ALIGN( sizeof(perfmon_smpl_hdr_t) + entries * (sizeof(perfmon_smpl_entry_t) + regcount*sizeof(u64))); /* * check requested size to avoid Denial-of-service attacks * XXX: may have to refine this test */ if (size > current->rlim[RLIMIT_MEMLOCK].rlim_cur) return -EAGAIN; /* find some free area in address space */ addr = get_unmapped_area(NULL, 0, size, 0, MAP_PRIVATE); if (!addr) goto no_addr; DBprintk((" entries=%ld aligned size=%ld, unmapped @0x%lx\n", entries, size, addr)); /* allocate vma */ vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL); if (!vma) goto no_vma; /* XXX: see rvmalloc() for page alignment problem */ smpl_buf = rvmalloc(size); if (smpl_buf == NULL) goto no_buffer; DBprintk((" smpl_buf @%p\n", smpl_buf)); if (pfm_remap_buffer((unsigned long)smpl_buf, addr, size)) goto cant_remap; /* allocate sampling buffer descriptor now */ psb = vmalloc(sizeof(*psb)); if (psb == NULL) goto no_buffer_desc; /* start with something clean */ memset(smpl_buf, 0x0, size); psb->psb_hdr = smpl_buf; psb->psb_addr = (char *)smpl_buf+sizeof(perfmon_smpl_hdr_t); /* first entry */ psb->psb_size = size; /* aligned size */ psb->psb_index = 0; psb->psb_entries = entries; atomic_set(&psb->psb_refcnt, 1); psb->psb_entry_size = sizeof(perfmon_smpl_entry_t) + regcount*sizeof(u64); DBprintk((" psb @%p entry_size=%ld hdr=%p addr=%p\n", (void *)psb,psb->psb_entry_size, (void *)psb->psb_hdr, (void *)psb->psb_addr)); /* initialize some of the fields of header */ psb->psb_hdr->hdr_version = PFM_SMPL_HDR_VERSION; psb->psb_hdr->hdr_entry_size = sizeof(perfmon_smpl_entry_t)+regcount*sizeof(u64); psb->psb_hdr->hdr_pmds = which_pmds; /* store which PMDS to record */ ctx->ctx_smpl_regs = which_pmds; /* link to perfmon context */ ctx->ctx_smpl_buf = psb; /* * initialize the vma for the sampling buffer */ vma->vm_mm = mm; vma->vm_start = addr; vma->vm_end = addr + size; vma->vm_flags = VM_READ|VM_MAYREAD; vma->vm_page_prot = PAGE_READONLY; /* XXX may need to change */ vma->vm_ops = NULL; vma->vm_pgoff = 0; vma->vm_file = NULL; vma->vm_raend = 0; vma->vm_private_data = ctx; /* link to pfm_context(not yet used) */ /* * now insert the vma in the vm list for the process */ insert_vm_struct(mm, vma); mm->total_vm += size >> PAGE_SHIFT; /* * that's the address returned to the user */ *user_addr = (void *)addr; return 0; /* outlined error handling */no_addr: DBprintk(("Cannot find unmapped area for size %ld\n", size)); return -ENOMEM;no_vma: DBprintk(("Cannot allocate vma\n")); return -ENOMEM;cant_remap: DBprintk(("Can't remap buffer\n")); rvfree(smpl_buf, size);no_buffer: DBprintk(("Can't allocate sampling buffer\n")); kmem_cache_free(vm_area_cachep, vma); return -ENOMEM;no_buffer_desc: DBprintk(("Can't allocate sampling buffer descriptor\n")); kmem_cache_free(vm_area_cachep, vma); rvfree(smpl_buf, size); return -ENOMEM;}static intpfx_is_sane(pfreq_context_t *pfx){ int ctx_flags; /* valid signal */ //if (pfx->notify_sig < 1 || pfx->notify_sig >= _NSIG) return -EINVAL; if (pfx->notify_sig !=0 && pfx->notify_sig != SIGPROF) return -EINVAL; /* cannot send to process 1, 0 means do not notify */ if (pfx->notify_pid < 0 || pfx->notify_pid == 1) return -EINVAL; ctx_flags = pfx->flags; if (ctx_flags & PFM_FL_SYSTEM_WIDE) {#ifdef CONFIG_SMP if (smp_num_cpus > 1) { printk("perfmon: system wide monitoring on SMP not yet supported\n"); return -EINVAL; }#endif if ((ctx_flags & PFM_FL_SMPL_OVFL_NOBLOCK) == 0) { printk("perfmon: system wide monitoring cannot use blocking notification mode\n"); return -EINVAL; } } /* probably more to add here */ return 0;}static intpfm_context_create(int flags, perfmon_req_t *req){ pfm_context_t *ctx; struct task_struct *task = NULL; perfmon_req_t tmp; void *uaddr = NULL; int ret; int ctx_flags; pid_t pid; /* to go away */ if (flags) { printk("perfmon: use context flags instead of perfmon() flags. Obsoleted API\n"); } if (copy_from_user(&tmp, req, sizeof(tmp))) return -EFAULT; ret = pfx_is_sane(&tmp.pfr_ctx); if (ret < 0) return ret; ctx_flags = tmp.pfr_ctx.flags; if (ctx_flags & PFM_FL_SYSTEM_WIDE) { /* * XXX: This is not AT ALL SMP safe */ if (pfs_info.pfs_proc_sessions > 0) return -EBUSY; if (pfs_info.pfs_sys_session > 0) return -EBUSY; pfs_info.pfs_sys_session = 1; } else if (pfs_info.pfs_sys_session >0) { /* no per-process monitoring while there is a system wide session */ return -EBUSY; } else pfs_info.pfs_proc_sessions++; ctx = pfm_context_alloc(); if (!ctx) goto error; /* record the creator (debug only) */ ctx->ctx_creator = current; pid = tmp.pfr_ctx.notify_pid; spin_lock_init(&ctx->ctx_notify_lock); if (pid == current->pid) { ctx->ctx_notify_task = task = current; current->thread.pfm_context = ctx; atomic_set(¤t->thread.pfm_notifiers_check, 1); } else if (pid!=0) { read_lock(&tasklist_lock); task = find_task_by_pid(pid); if (task) { /* * record who to notify */ ctx->ctx_notify_task = task; /* * make visible * must be done inside critical section * * if the initialization does not go through it is still * okay because child will do the scan for nothing which * won't hurt. */ current->thread.pfm_context = ctx; /* * will cause task to check on exit for monitored * processes that would notify it. see release_thread() * Note: the scan MUST be done in release thread, once the * task has been detached from the tasklist otherwise you are * exposed to race conditions. */ atomic_add(1, &task->thread.pfm_notifiers_check); } read_unlock(&tasklist_lock); } /* * notification process does not exist */ if (pid != 0 && task == NULL) { ret = -EINVAL; goto buffer_error; } ctx->ctx_notify_sig = SIGPROF; /* siginfo imposes a fixed signal */ if (tmp.pfr_ctx.smpl_entries) { DBprintk((" sampling entries=%ld\n",tmp.pfr_ctx.smpl_entries)); ret = pfm_smpl_buffer_alloc(ctx, tmp.pfr_ctx.smpl_regs, tmp.pfr_ctx.smpl_entries, &uaddr); if (ret<0) goto buffer_error; tmp.pfr_ctx.smpl_vaddr = uaddr; } /* initialization of context's flags */ ctx->ctx_fl_inherit = ctx_flags & PFM_FL_INHERIT_MASK; ctx->ctx_fl_noblock = (ctx_flags & PFM_FL_SMPL_OVFL_NOBLOCK) ? 1 : 0; ctx->ctx_fl_system = (ctx_flags & PFM_FL_SYSTEM_WIDE) ? 1: 0; ctx->ctx_fl_exclintr = (ctx_flags & PFM_FL_EXCL_INTR) ? 1: 0; ctx->ctx_fl_frozen = 0; /* * Keep track of the pmds we want to sample * XXX: may be we don't need to save/restore the DEAR/IEAR pmds * but we do need the BTB for sure. This is because of a hardware * buffer of 1 only for non-BTB pmds. */ ctx->ctx_used_pmds[0] = tmp.pfr_ctx.smpl_regs; ctx->ctx_used_pmcs[0] = 1; /* always save/restore PMC[0] */ sema_init(&ctx->ctx_restart_sem, 0); /* init this semaphore to locked */ if (copy_to_user(req, &tmp, sizeof(tmp))) { ret = -EFAULT; goto buffer_error; } DBprintk((" context=%p, pid=%d notify_sig %d notify_task=%p\n",(void *)ctx, current->pid, ctx->ctx_notify_sig, ctx->ctx_notify_task)); DBprintk((" context=%p, pid=%d flags=0x%x inherit=%d noblock=%d system=%d\n",(void *)ctx, current->pid, ctx_flags, ctx->ctx_fl_inherit, ctx->ctx_fl_noblock, ctx->ctx_fl_system)); /* * when no notification is required, we can make this visible at the last moment */ if (pid == 0) current->thread.pfm_context = ctx; /* * by default, we always include interrupts for system wide * DCR.pp is set by default to zero by kernel in cpu_init() */ if (ctx->ctx_fl_system) { if (ctx->ctx_fl_exclintr == 0) { unsigned long dcr = ia64_get_dcr(); ia64_set_dcr(dcr|IA64_DCR_PP); /* * keep track of the kernel default value */ pfs_info.pfs_dfl_dcr = dcr; DBprintk((" dcr.pp is set\n")); } } return 0;buffer_error: pfm_context_free(ctx);error: /* * undo session reservation */ if (ctx_flags & PFM_FL_SYSTEM_WIDE) { pfs_info.pfs_sys_session = 0; } else { pfs_info.pfs_proc_sessions--; } return ret;}static voidpfm_reset_regs(pfm_context_t *ctx){ unsigned long mask = ctx->ctx_ovfl_regs; int i, cnum; DBprintk((" ovfl_regs=0x%lx\n", mask)); /* * now restore reset value on sampling overflowed counters */ for(i=0, cnum=PMU_FIRST_COUNTER; i < pmu_conf.max_counters; i++, cnum++, mask >>= 1) { if (mask & 0x1) { DBprintk((" reseting PMD[%d]=%lx\n", cnum, ctx->ctx_pmds[i].smpl_rval & pmu_conf.perf_ovfl_val)); /* upper part is ignored on rval */ ia64_set_pmd(cnum, ctx->ctx_pmds[i].smpl_rval); /* * we must reset BTB index (clears pmd16.full to make * sure we do not report the same branches twice. * The non-blocking case in handled in update_counters() */ if (cnum == ctx->ctx_btb_counter) { DBprintk(("reseting PMD16\n")); ia64_set_pmd(16, 0); } } } /* just in case ! */ ctx->ctx_ovfl_regs = 0;}static intpfm_write_pmcs(struct task_struct *ta, perfmon_req_t *req, int count){ struct thread_struct *th = &ta->thread; pfm_context_t *ctx = th->pfm_context; perfmon_req_t tmp; unsigned long cnum; int i; /* XXX: ctx locking may be required here */ for (i = 0; i < count; i++, req++) { if (copy_from_user(&tmp, req, sizeof(tmp))) return -EFAULT; cnum = tmp.pfr_reg.reg_num; /* XXX needs to check validity of the data maybe */ if (!PMC_IS_IMPL(cnum)) { DBprintk((" invalid pmc[%ld]\n", cnum)); return -EINVAL; } if (PMC_IS_COUNTER(cnum)) { /* * we keep track of EARS/BTB to speed up sampling later */ if (PMC_IS_DEAR(&tmp.pfr_reg.reg_value)) { ctx->ctx_dear_counter = cnum; } else if (PMC_IS_IEAR(&tmp.pfr_reg.reg_value)) { ctx->ctx_iear_counter = cnum; } else if (PMC_IS_BTB(&tmp.pfr_reg.reg_value)) { ctx->ctx_btb_counter = cnum; }#if 0 if (tmp.pfr_reg.reg_flags & PFM_REGFL_OVFL_NOTIFY) ctx->ctx_pmds[cnum - PMU_FIRST_COUNTER].flags |= PFM_REGFL_OVFL_NOTIFY;#endif } /* keep track of what we use */ CTX_USED_PMC(ctx, cnum); ia64_set_pmc(cnum, tmp.pfr_reg.reg_value); DBprintk((" setting PMC[%ld]=0x%lx flags=0x%x used_pmcs=0%lx\n", cnum, tmp.pfr_reg.reg_value, ctx->ctx_pmds[cnum - PMU_FIRST_COUNTER].flags, ctx->ctx_used_pmcs[0])); } /* * we have to set this here event hough we haven't necessarily started monitoring * because we may be context switched out */ if (ctx->ctx_fl_system==0) th->flags |= IA64_THREAD_PM_VALID; return 0;}static intpfm_write_pmds(struct task_struct *ta, perfmon_req_t *req, int count){ struct thread_struct *th = &ta->thread; pfm_context_t *ctx = th->pfm_context; perfmon_req_t tmp; unsigned long cnum; int i; /* XXX: ctx locking may be required here */ for (i = 0; i < count; i++, req++) { int k; if (copy_from_user(&tmp, req, sizeof(tmp))) return -EFAULT; cnum = tmp.pfr_reg.reg_num; k = cnum - PMU_FIRST_COUNTER; if (!PMD_IS_IMPL(cnum)) return -EINVAL; /* update virtualized (64bits) counter */ if (PMD_IS_COUNTER(cnum)) { ctx->ctx_pmds[k].ival = tmp.pfr_reg.reg_value; ctx->ctx_pmds[k].val = tmp.pfr_reg.reg_value & ~pmu_conf.perf_ovfl_val; ctx->ctx_pmds[k].smpl_rval = tmp.pfr_reg.reg_smpl_reset; ctx->ctx_pmds[k].ovfl_rval = tmp.pfr_reg.reg_ovfl_reset; if (tmp.pfr_reg.reg_flags & PFM_REGFL_OVFL_NOTIFY) ctx->ctx_pmds[cnum - PMU_FIRST_COUNTER].flags |= PFM_REGFL_OVFL_NOTIFY; } /* keep track of what we use */ CTX_USED_PMD(ctx, cnum); /* writes to unimplemented part is ignored, so this is safe */ ia64_set_pmd(cnum, tmp.pfr_reg.reg_value); /* to go away */ ia64_srlz_d(); DBprintk((" setting PMD[%ld]: ovfl_notify=%d pmd.val=0x%lx pmd.ovfl_rval=0x%lx pmd.smpl_rval=0x%lx pmd=%lx used_pmds=0%lx\n", cnum, PMD_OVFL_NOTIFY(ctx, cnum - PMU_FIRST_COUNTER), ctx->ctx_pmds[k].val, ctx->ctx_pmds[k].ovfl_rval, ctx->ctx_pmds[k].smpl_rval, ia64_get_pmd(cnum) & pmu_conf.perf_ovfl_val, ctx->ctx_used_pmds[0]));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -