您当前的位置:首页 > IT编程 > C++
| C语言 | Java | VB | VC | python | Android | TensorFlow | C++ | oracle | 学术与代码 | cnn卷积神经网络 | gnn | 图像修复 | Keras | 数据集 | Neo4j | 自然语言处理 | 深度学习 | 医学CAD | 医学影像 | 超参数 | pointnet | pytorch | 异常检测 | Transformers | 情感分类 | 知识图谱 |

自学教程:C++ spinlock_release函数代码示例

51自学网 2021-06-03 08:10:31
  C++
这篇教程C++ spinlock_release函数代码示例写得很实用,希望能帮到您。

本文整理汇总了C++中spinlock_release函数的典型用法代码示例。如果您正苦于以下问题:C++ spinlock_release函数的具体用法?C++ spinlock_release怎么用?C++ spinlock_release使用的例子?那么恭喜您, 这里精选的函数代码示例或许可以为您提供帮助。

在下文中一共展示了spinlock_release函数的30个代码示例,这些例子默认根据受欢迎程度排序。您可以为喜欢或者感觉有用的代码点赞,您的评价将有助于我们的系统推荐出更棒的C++代码示例。

示例1: service_isvalid

/** * Check to see if a service pointer is valid * * @param service	The pointer to check * @return 1 if the service is in the list of all services */intservice_isvalid(SERVICE *service){SERVICE		*ptr;int		rval = 0;	spinlock_acquire(&service_spin);	ptr = allServices;	while (ptr)	{		if (ptr == service)		{			rval = 1;			break;		}		ptr = ptr->next;	}	spinlock_release(&service_spin);	return rval;}
开发者ID:Robert-Xie,项目名称:MaxScale,代码行数:26,


示例2: lock_do_i_hold

boollock_do_i_hold(struct lock *lock){        // Write this	if (!CURCPU_EXISTS()) {			return true;		}//	if(lock->lk_holder== curcpu->c_self){//		return true;//	}	//KASSERT(lock->lk_thread != NULL);	//KASSERT(lock != NULL);	bool value= false;        //(void)lock;  // suppress warning until code gets written	spinlock_acquire(&lock->lk_spinlock);        value= (lock->lk_thread== curthread); // dummy until code gets written        spinlock_release(&lock->lk_spinlock);        return value;}
开发者ID:coderpm,项目名称:OS161,代码行数:20,


示例3: gwbuf_add_hint

/** * Add hint to a buffer. * * @param buf	The buffer to add the hint to * @param hint	The hint itself * @return	Non-zero on success */intgwbuf_add_hint(GWBUF *buf, HINT *hint){    HINT	*ptr;    spinlock_acquire(&buf->gwbuf_lock);    if (buf->hint)    {        ptr = buf->hint;        while (ptr->next)            ptr = ptr->next;        ptr->next = hint;    }    else    {        buf->hint = hint;    }    spinlock_release(&buf->gwbuf_lock);    return 1;}
开发者ID:hholzgra,项目名称:MaxScale,代码行数:27,


示例4: rses_begin_locked_router_action

/**  * @node Acquires lock to router client session if it is not closed. * * Parameters: * @param rses - in, use *           * * @return true if router session was not closed. If return value is true * it means that router is locked, and must be unlocked later. False, if * router was closed before lock was acquired. * *  * @details (write detailed description here) * */static bool rses_begin_locked_router_action(        ROUTER_CLIENT_SES* rses){        bool succp = false;                CHK_CLIENT_RSES(rses);        if (rses->rses_closed) {                goto return_succp;        }               spinlock_acquire(&rses->rses_lock);        if (rses->rses_closed) {                spinlock_release(&rses->rses_lock);                goto return_succp;        }        succp = true;        return_succp:        return succp;}
开发者ID:abiratsis,项目名称:MaxScale,代码行数:35,


示例5: monitorList

/** * List all the monitors * * @param dcb	DCB for printing output */voidmonitorList(DCB *dcb){MONITOR	*ptr;	spinlock_acquire(&monLock);	ptr = allMonitors;	dcb_printf(dcb, "---------------------+---------------------/n");	dcb_printf(dcb, "%-20s | Status/n", "Monitor");	dcb_printf(dcb, "---------------------+---------------------/n");	while (ptr)	{		dcb_printf(dcb, "%-20s | %s/n", ptr->name,			ptr->state & MONITOR_STATE_RUNNING					? "Running" : "Stopped");		ptr = ptr->next;	}	dcb_printf(dcb, "---------------------+---------------------/n");	spinlock_release(&monLock);}
开发者ID:balsdorf,项目名称:MaxScale,代码行数:25,


示例6: cputs

// `High'-level console I/O.  Used by readline and cprintf.voidcputs(const char *str){	if (read_cs() & 3)		return sys_cputs(str);	// use syscall from user mode	// Hold the console spinlock while printing the entire string,	// so that the output of different cputs calls won't get mixed.	// Implement ad hoc recursive locking for debugging convenience.	bool already = spinlock_holding(&cons_lock);	if (!already)		spinlock_acquire(&cons_lock);	char ch;	while (*str)		cons_putc(*str++);	if (!already)		spinlock_release(&cons_lock);}
开发者ID:khanotations,项目名称:pios,代码行数:21,


示例7: cpustatus_generate_irq

void cpustatus_generate_irq(device_t *dev){  interrupt_status_t intr_status;  volatile cpu_io_area_t *iobase = (cpu_io_area_t *)dev->io_address;  cpu_real_device_t *cpu = (cpu_real_device_t *)dev->real_device;  KERNEL_ASSERT(dev != NULL && cpu != NULL);  intr_status = _interrupt_disable();  spinlock_acquire(&cpu->slock);  /* If you really want to do something with inter-cpu interrupts,     do it here.*/  /* Generate the IRQ */  iobase->command = CPU_COMMAND_RAISE_IRQ;  spinlock_release(&cpu->slock);  _interrupt_set_state(intr_status);}
开发者ID:DIKU-EDU,项目名称:kudos,代码行数:20,


示例8: lamebus_detach_interrupt

/* * Unregister a function that was being called when a particular slot * signaled an interrupt. */voidlamebus_detach_interrupt(struct lamebus_softc *sc, int slot){	uint32_t mask = ((uint32_t)1) << slot;	KASSERT(slot>=0 && slot < LB_NSLOTS);	spinlock_acquire(&sc->ls_lock);	if ((sc->ls_slotsinuse & mask)==0) {		panic("lamebus_detach_interrupt: slot %d not marked in use/n",		      slot);	}	KASSERT(sc->ls_irqfuncs[slot]!=NULL);	sc->ls_devdata[slot] = NULL;	sc->ls_irqfuncs[slot] = NULL;		spinlock_release(&sc->ls_lock);}
开发者ID:Adam-Koza,项目名称:A3,代码行数:24,


示例9: thread_startup

/* * This function is where new threads start running. The arguments * ENTRYPOINT, DATA1, and DATA2 are passed through from thread_fork. * * Because new code comes here from inside the middle of * thread_switch, the beginning part of this function must match the * tail of thread_switch. */voidthread_startup(void (*entrypoint)(void *data1, unsigned long data2),	       void *data1, unsigned long data2){	struct thread *cur;	cur = curthread;	/* Clear the wait channel and set the thread state. */	cur->t_wchan_name = NULL;	cur->t_state = S_RUN;	/* Release the runqueue lock acquired in thread_switch. */	spinlock_release(&curcpu->c_runqueue_lock);	/* Activate our address space in the MMU. */	as_activate();	/* Clean up dead threads. */	exorcise();	/* Enable interrupts. */	spl0();#if OPT_SYNCHPROBS	/* Yield a random number of times to get a good mix of threads. */	{		int i, n;		n = random()%161 + random()%161;		for (i=0; i<n; i++) {			thread_yield();		}	}#endif	/* Call the function. */	entrypoint(data1, data2);	/* Done. */	thread_exit();}
开发者ID:sfurrow88,项目名称:cs571_sfurrow,代码行数:49,


示例10: freeSession

/** * @node Unlink from backend server, unlink from router's connection list, * and free memory of a router client session.   * * Parameters: * @param router - <usage> *          <description> * * @param router_cli_ses - <usage> *          <description> * * @return void * *  * @details (write detailed description here) * */static void freeSession(        ROUTER* router_instance,        void*   router_client_ses){        ROUTER_INSTANCE*   router = (ROUTER_INSTANCE *)router_instance;        ROUTER_CLIENT_SES* router_cli_ses =                (ROUTER_CLIENT_SES *)router_client_ses;        int prev_val;                prev_val = atomic_add(&router_cli_ses->backend->current_connection_count, -1);        ss_dassert(prev_val > 0);        	spinlock_acquire(&router->lock);        	if (router->connections == router_cli_ses) {		router->connections = router_cli_ses->next;        } else {		ROUTER_CLIENT_SES *ptr = router->connections;                		while (ptr != NULL && ptr->next != router_cli_ses) {			ptr = ptr->next;                }                		if (ptr != NULL) {			ptr->next = router_cli_ses->next;                }	}	spinlock_release(&router->lock);        LOGIF(LD, (skygw_log_write_flush(                LOGFILE_DEBUG,                "%lu [freeSession] Unlinked router_client_session %p from "                "router %p and from server on port %d. Connections : %d. ",                pthread_self(),                router_cli_ses,                router,                router_cli_ses->backend->server->port,                prev_val-1)));        free(router_cli_ses);}
开发者ID:jeffraska,项目名称:MaxScale,代码行数:58,


示例11: hashtable_write_lock

/** * Obtain an exclusive write lock for the hash table. * * We acquire the hashtable spinlock, check for the number of * readers beign zero. If it is not we hold the spinlock and * loop waiting for the n_readers to reach zero. This will prevent * any new readers beign granted access but will not prevent current * readers releasing the read lock. * * Once we have no readers we increment writelock and test if we are * the only writelock holder, if not we repeat the process. We hold * the spinlock throughout the process since both read and write * locks do not require the spinlock to be acquired. * * @param table The table to lock for updates */static voidhashtable_write_lock(HASHTABLE *table){    int available;    spinlock_acquire(&table->spin);    do    {        while (table->n_readers)        {            ;        }        available = atomic_add(&table->writelock, 1);        if (available != 0)        {            atomic_add(&table->writelock, -1);        }    }    while (available != 0);    spinlock_release(&table->spin);}
开发者ID:DBMSRmutl,项目名称:MaxScale,代码行数:37,


示例12: timer_read

// Read and returns the number of 1.193182MHz ticks since kernel boot.// This function also updates the high-order bits of our tick count,// so it MUST be called at least once per 1/18 sec.uint64_ttimer_read(void){	spinlock_acquire(&lock);	// Read the current timer counter.	outb(TIMER_MODE, TIMER_SEL0 | TIMER_LATCH);	uint8_t lo = inb(IO_TIMER1);	uint8_t hi = inb(IO_TIMER1);	uint16_t ctr = hi << 8 | lo;	assert(ctr != 0);	// If the counter has wrapped, assume we're into the next tick.	if (ctr > last)		base += 65535;	last = ctr;	uint64_t ticks = base + (65535 - ctr);	spinlock_release(&lock);	return ticks;}
开发者ID:Mic92,项目名称:Determinator,代码行数:24,


示例13: thread_finish

/** Perform suicide. The calling thread will kill itself by freeing * its memory and other resources and marking itself as dying. The * scheduler will free the thread table entry when it encounters dying * threads. */void thread_finish(void){    TID_t my_tid;    my_tid = thread_get_current_thread();    _interrupt_disable();    /* Check that the page mappings have been cleared. */    KERNEL_ASSERT(thread_table[my_tid].pagetable == NULL);    spinlock_acquire(&thread_table_slock);    thread_table[my_tid].state = THREAD_DYING;    spinlock_release(&thread_table_slock);    _interrupt_enable();    _interrupt_generate_sw0();    /* not possible without a stack? alternative in assembler? */    KERNEL_PANIC("thread_finish(): thread was not destroyed");}
开发者ID:Amr116,项目名称:Buenos,代码行数:26,


示例14: wchan_destroy

/* * Destroy a wait channel. Must be empty and unlocked. * (The corresponding cleanup functions require this.) */voidwchan_destroy(struct wchan* wc) {    unsigned num;    struct wchan* wc2;    /* remove from allwchans[] */    spinlock_acquire(&allwchans_lock);    num = wchanarray_num(&allwchans);    assert(wchanarray_get(&allwchans, wc->wc_index) == wc);    if (wc->wc_index < num - 1) {        /* move the last entry into our slot */        wc2 = wchanarray_get(&allwchans, num - 1);        wchanarray_set(&allwchans, wc->wc_index, wc2);        wc2->wc_index = wc->wc_index;    }    wchanarray_setsize(&allwchans, num - 1);    spinlock_release(&allwchans_lock);    threadlist_cleanup(&wc->wc_threads);    kfree(wc);}
开发者ID:plurmiscuous,项目名称:JinxOS,代码行数:25,


示例15: monitor_free

/** * Free a monitor, first stop the monitor and then remove the monitor from * the chain of monitors and free the memory. * * @param mon	The monitor to free */voidmonitor_free(MONITOR *mon){MONITOR	*ptr;	mon->module->stopMonitor(mon->handle);	spinlock_acquire(&monLock);	if (allMonitors == mon)		allMonitors = mon->next;	else	{		ptr = allMonitors;		while (ptr->next && ptr->next != mon)			ptr = ptr->next;		if (ptr->next)			ptr->next = mon->next;	}	spinlock_release(&monLock);	free(mon->name);	free(mon);}
开发者ID:MassimilianoPinto,项目名称:MaxScale,代码行数:27,


示例16: vm_printstats

voidvm_printstats(void){	uint32_t zf, mn, mj, de, we, te;	spinlock_acquire(&stats_spinlock);	zf = ct_zerofills;	mn = ct_minfaults;	mj = ct_majfaults;	de = ct_discard_evictions;	we = ct_write_evictions;	spinlock_release(&stats_spinlock);	te = de+we;	kprintf("vm: %lu zerofills %lu minorfaults %lu majorfaults/n",		(unsigned long) zf, (unsigned long) mn, (unsigned long) mj);	kprintf("vm: %lu evictions (%lu discarding, %lu writes)/n",		(unsigned long) te, (unsigned long) de, (unsigned long) we);	vm_printmdstats();}
开发者ID:ShaoyuC,项目名称:OS161,代码行数:21,


示例17: block

/* * q is a pointer to the waiting list where current_running should be * inserted. */void block(pcb_t ** q, int *spinlock){  pcb_t *tmp;  enter_critical();  if (spinlock)    spinlock_release(spinlock);  /* mark the job as blocked */  current_running->status = BLOCKED;  /* Insert into waiting list */  tmp = *q;  (*q) = current_running;  current_running->next_blocked = tmp;  /* remove job from ready queue, pick next job to run and dispatch it */  scheduler_entry();  leave_critical();}
开发者ID:robertsami,项目名称:proj,代码行数:25,


示例18: newSession

/** * Associate a new session with this instance of the filter and opens * a connection to the server and prepares the exchange and the queue for use. * * * @param instance	The filter instance data * @param session	The session itself * @return Session specific data for this session */static	void	*newSession(FILTER *instance, SESSION *session){  MQ_INSTANCE	*my_instance = (MQ_INSTANCE *)instance;  MQ_SESSION	*my_session;    if ((my_session = calloc(1, sizeof(MQ_SESSION))) != NULL){    if((my_session->conn = amqp_new_connection()) == NULL){      free(my_session);      return NULL;    }    spinlock_acquire(my_instance->rconn_lock);      init_conn(my_instance,my_session);    my_session->was_query = 0;    my_session->uid = NULL;    spinlock_release(my_instance->rconn_lock);        }  return my_session;}
开发者ID:markus456,项目名称:MaxScale,代码行数:30,


示例19: cv_wait

voidcv_wait(struct cv *cv, struct lock *lock){#if OPT_A1    KASSERT(cv != NULL && lock != NULL);    KASSERT(curthread->t_in_interrupt == false);    spinlock_acquire(&cv->cv_lock);    // atomically perform:    // a) releasing the lock    // b) locking the wait channel for cv    // b) pushing the thread to sleep on the wait channel    lock_release(lock);    wchan_lock(cv->cv_wchan);    spinlock_release(&cv->cv_lock);    wchan_sleep(cv->cv_wchan);    // when switching back, reacquire the lock to return    // to the previous state.    lock_acquire(lock);#endif}
开发者ID:shaon0000,项目名称:massive-wookie,代码行数:21,


示例20: session_isvalid

/** * Check to see if a session is valid, i.e. in the list of all sessions * * @param session	Session to check * @return		1 if the session is valid otherwise 0 */intsession_isvalid(SESSION *session){SESSION		*ptr;int		rval = 0;	spinlock_acquire(&session_spin);	ptr = allSessions;	while (ptr)	{		if (ptr == session)		{			rval = 1;			break;		}		ptr = ptr->next;	}	spinlock_release(&session_spin);	return rval;}
开发者ID:abiratsis,项目名称:MaxScale,代码行数:27,


示例21: lock_release

voidlock_release(struct lock *lock){        // Write this        //disable interups                       KASSERT(lock!= NULL);        //KASSERT(lock_do_i_hold(lock));                        spinlock_acquire(&lock->splk);        lock->currentplace = NULL;      //  lock->lock_hold = 0;        wchan_wakeone(lock->lock_wchan);        spinlock_release(&lock->splk);        //wake up thread?        //(void)lock;  // suppress warning until code gets written}
开发者ID:hccyang,项目名称:OS-system,代码行数:21,


示例22: ipi_tlbshootdown

voidipi_tlbshootdown(struct cpu *target, const struct tlbshootdown *mapping){	int n;	spinlock_acquire(&target->c_ipi_lock);	n = target->c_numshootdown;	if (n == TLBSHOOTDOWN_MAX) {		target->c_numshootdown = TLBSHOOTDOWN_ALL;	}	else {		target->c_shootdown[n] = *mapping;		target->c_numshootdown = n+1;	}	target->c_ipi_pending |= (uint32_t)1 << IPI_TLBSHOOTDOWN;	mainbus_send_ipi(target);	spinlock_release(&target->c_ipi_lock);}
开发者ID:sfurrow88,项目名称:cs571_sfurrow,代码行数:21,


示例23: lock_release

voidlock_release(struct lock *lock){        // Write this		//Peng 2.19.2016        KASSERT(lock != NULL);        spinlock_acquire(&lock->lock_splk);        KASSERT(lock_do_i_hold(lock));        if (lock->held)            kprintf("true");        else            kprintf("false");                       lock->held=false;        lock->holder=NULL;        KASSERT(!lock->held);		wchan_wakeone(lock->lock_wchan, &lock->lock_splk);        spinlock_release(&lock->lock_splk);		//Peng        //(void)lock;  // suppress warning until code gets written}
开发者ID:patricksu,项目名称:repo3,代码行数:21,


示例24: session_isvalid

/** * Check to see if a session is valid, i.e. in the list of all sessions * * @param session       Session to check * @return              1 if the session is valid otherwise 0 */intsession_isvalid(SESSION *session){    SESSION *list_session;    int rval = 0;    spinlock_acquire(&session_spin);    list_session = allSessions;    while (list_session)    {        if (list_session == session)        {            rval = 1;            break;        }        list_session = list_session->next;    }    spinlock_release(&session_spin);    return rval;}
开发者ID:bigshuai,项目名称:MaxScale,代码行数:27,


示例25: physmem_freeblocks

void physmem_freeblocks(void *ptr, uint32_t size){  /* Calculate frame */  uint64_t addr = (uint64_t)ptr, i;  int64_t frame = (int64_t)(addr / PMM_BLOCK_SIZE);  /* Get lock */  interrupt_status_t intr_status = _interrupt_disable();  spinlock_acquire(physmem_lock);  /* Free */  for(i = 0; i < size; i++)    memmap_unsetbit(frame + i);  /* Release spinlock */  spinlock_release(physmem_lock);  _interrupt_set_state(intr_status);  /* Stats */  used_blocks -= size;}
开发者ID:PtxDK,项目名称:OSM,代码行数:21,


示例26: semaphore_ac_break

voidsemaphore_ac_break(semaphore_t semaphore, ips_node_t node){    int irq = __irq_save();    spinlock_acquire(&semaphore->lock);    if (IPS_NODE_WAIT(node) && IPS_NODE_AC_WAIT(node))    {        node->next->prev = node->next;        node->prev->next = node->prev;        if (SEMAPHORE_PTR(semaphore) == node)        {            if (node->next == node)                SEMAPHORE_WAIT_CLEAR(semaphore);            else SEMAPHORE_PTR_SET(semaphore, node->next);        }    }    spinlock_release(&semaphore->lock);    __irq_restore(irq);    IPS_NODE_AC_WAIT_CLEAR(node);}
开发者ID:xinhaoyuan,项目名称:cox,代码行数:21,


示例27: vm_tlbshootdown

/* * Do one TLB shootdown. */voidvm_tlbshootdown(const struct tlbshootdown *ts, int num){	int i;	int tlbix;	unsigned where;	spinlock_acquire(&coremap_spinlock);	ct_shootdown_interrupts++;	for (i=0; i<num; i++) {		tlbix = ts[i].ts_tlbix;		where = ts[i].ts_coremapindex;		if (coremap[where].cm_tlbix == tlbix &&		    coremap[where].cm_cpunum == curcpu->c_number) {			tlb_invalidate(tlbix);			ct_shootdowns_done++;		}	}	wchan_wakeall(coremap_shootchan);	spinlock_release(&coremap_spinlock);}
开发者ID:YueGan,项目名称:CSCC69A2,代码行数:24,


示例28: coremap_ufree

voidcoremap_ufree(paddr_t page_addr) {	KASSERT(page_addr!=31);	int entry = (page_addr-ram_start)/PAGE_SIZE;	struct cm_entry* cm_begin = (struct cm_entry*)PADDR_TO_KVADDR(coremap);		spinlock_acquire(&cm_lock);	//int before = coremap_ucount();	cm_begin[entry].in_use = 0;	cm_begin[entry].pg = NULL;	coremap_clean_page(PADDR_TO_KVADDR(page_addr));	free_mem_pages++;	//int after = coremap_ucount();		//KASSERT(before-1 == after);	spinlock_release(&cm_lock);}
开发者ID:ginobuzz,项目名称:os161,代码行数:21,


示例29: process_finish

void process_finish(int retval) {    interrupt_status_t intr_status;    process_id_t cur = process_get_current_process();    thread_table_t *thread = thread_get_current_thread_entry();    intr_status = _interrupt_disable();    spinlock_acquire(&process_table_slock);    process_table[cur].state = PROCESS_ZOMBIE;    process_table[cur].retval = retval;    /* Remember to destroy the pagetable! */    vm_destroy_pagetable(thread->pagetable);    thread->pagetable = NULL;    sleepq_wake_all(&process_table[cur]);    spinlock_release(&process_table_slock);    _interrupt_set_state(intr_status);    thread_finish();}
开发者ID:cfrost,项目名称:buenos,代码行数:21,


示例30: acrn_update_ucode

void acrn_update_ucode(struct acrn_vcpu *vcpu, uint64_t v){	uint64_t gva, fault_addr = 0UL;	struct ucode_header uhdr;	size_t data_size;	int32_t err;	uint32_t err_code;	spinlock_obtain(&micro_code_lock);	gva = v - sizeof(struct ucode_header);	err_code = 0U;	err = copy_from_gva(vcpu, &uhdr, gva, sizeof(uhdr), &err_code,			&fault_addr);	if (err < 0) {		if (err == -EFAULT) {			vcpu_inject_pf(vcpu, fault_addr, err_code);		}	} else {		data_size = get_ucode_data_size(&uhdr) + sizeof(struct ucode_header);		if (data_size > MICRO_CODE_SIZE_MAX) {			pr_err("The size of microcode is greater than 0x%x",					MICRO_CODE_SIZE_MAX);		} else {			err_code = 0U;			err = copy_from_gva(vcpu, micro_code, gva, data_size, &err_code,					&fault_addr);			if (err < 0) {				if (err == -EFAULT) {					vcpu_inject_pf(vcpu, fault_addr, err_code);				}			} else {				msr_write(MSR_IA32_BIOS_UPDT_TRIG,					(uint64_t)micro_code + sizeof(struct ucode_header));				(void)get_microcode_version();			}		}	}	spinlock_release(&micro_code_lock);}
开发者ID:rossburton,项目名称:acrn-hypervisor,代码行数:40,



注:本文中的spinlock_release函数示例整理自Github/MSDocs等源码及文档管理平台,相关代码片段筛选自各路编程大神贡献的开源项目,源码版权归原作者所有,传播和使用请参考对应项目的License;未经允许,请勿转载。


C++ spinner函数代码示例
C++ spinlock_init函数代码示例
万事OK自学网:51自学网_软件自学网_CAD自学网自学excel、自学PS、自学CAD、自学C语言、自学css3实例,是一个通过网络自主学习工作技能的自学平台,网友喜欢的软件自学网站。