信号量的实现和应用

2023-05-16

信号量的实现和应用

一、实验环境

​ 本次实验的操作环境还是一样的实验环境。环境文件如下:

image-20211104204808701

如果不清楚的话请参考往期博客。

二、实验目标与内容

1、目标:

  1. 加深对进程同步与互斥概念的认识;

  2. 掌握信号量的使用,并应用它解决生产者——消费者问题;

  3. 掌握信号量的实现原理。

2、内容:

本次实验的基本内容是:

  1. Ubuntu下编写程序,用信号量解决生产者——消费者问题;
  2. linux-0.11中实现信号量,用生产者—消费者程序检验之。

(1)用信号量解决生产者——消费者问题

Ubuntu上编写应用程序pc.c,解决经典的生产者—消费者问题,完成下面的功能:

  1. 建立一个生产者进程,N个消费者进程(N>1);
  2. 用文件建立一个共享缓冲区;
  3. 生产者进程依次向缓冲区写入整数0,1,2,…,M,M>=500;
  4. 消费者进程从缓冲区读数,每次读一个,并将读出的数字从缓冲区删除,然后将本进程ID和数字输出到标准输出;
  5. 缓冲区同时最多只能保存10个数。

一种可能的输出效果是:

10: 0
10: 1
10: 2
10: 3
10: 4
11: 5
11: 6
12: 7
10: 8
12: 9
12: 10
12: 11
12: 12
……
11: 498
11: 499

其中ID的顺序会有较大变化,但冒号后的数字一定是从0开始递增加一的。

(2)实现信号量

Linux0.11版还没有实现信号量,Linus把这件富有挑战的工作留给了你。如果能实现一套山寨版的完全符合POSIX规范的信号量, 无疑是很有成就感的。但时间暂时不允许我们这么做,所以先弄一套缩水版的类POSIX信号量,它的函数原型和标准并不完全相同, 而且只包含如下系统调用:

sem_t *sem_open(const char *name, unsigned int value);
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_unlink(const char *name);

kernel 目录下新建 sem.c 文件实现如上功能。然后将 pc.cUbuntu 移植到0.11 下,测试自己实现的信号量。

三、实验原理

1、进程同步与互斥

(1)什么是进程同步

在多道批处理系统中,多个进程是可以并发执行的,但由于系统的资源有限,进程的执行不是一贯到底的, 而是走走停停,以不可预知的速度向前推进,这就是进程的**「异步性」**。

那么,「进程的异步性会带来什么问题呢」?举个例子,如果有 A、B 两个进程分别负责读和写数据的操作,这两个线程是相互合作、相互依赖的。那么写数据应该发生在读数据之前。而实际上,由于异步性的存在,可能会发生先读后写的情况,而此时由于缓冲区还没有被写入数据,读进程 A 没有数据可读,因此读进程 A 被阻塞。

image-20211104211006667

进程同步(synchronization)就是用来解决这个问题的。从上面的例子我们能看出,一个进程的执行可能影响到另一个进程的执行,「所谓进程同步就是指协调这些完成某个共同任务的并发线程,在某些位置上指定线程的先后执行次序、传递信号或消息」

举个生活中的进程同步的例子,你想要喝热水,于是你打了一壶水开始烧,在这壶水烧开之前,你只能一直等着,水烧开之后水壶自然会发生响声提醒你来喝水,于是你就可以喝水了。就是说**「水烧开这个事情必须发生在你喝水之前」**。

注意不要把进程同步和进程调度搞混了:

  • 进程调度是为了最大程度的利用 CPU 资源,选用合适的算法调度就绪队列中的进程。
  • 进程同步是为了协调一些进程以完成某个任务,比如读和写,你肯定先写后读,不能先读后写吧,这就是进程同步做的事情了,指定这些进程的先后执行次序使得某个任务能够顺利完成。
(2)什么是进程互斥

同样的,也是因为进程的并发性,并发执行的线程不可避免地需要共享一些系统资源,比如内存、打印机、摄像头等。举个例子:我们去学校打印店打印论文,你按下了 WPS 的 “打印” 选项,于是打印机开始工作。你的论文打印到一半时,另一位同学按下了 Word 的 “打印” 按钮,开始打印他自己的论文。想象一下如果两个进程可以随意的、并发的共享打印机资源,会发生什么情况?

显然,两个进程并发运行,导致打印机设备交替的收到 WPS 和 Word 两个进程发来的打印请求,结果两篇论文的内容混杂在一起了。

进程互斥(mutual exclusion)就是用来解决这个问题的。当某个进程 A 在访问打印机时,如果另一个进程 B 也想要访问打印机,它就必须等待,直到 A 进程访问结束并释放打印机资源后,B 进程才能去访问。

实际上,像上述的打印机这种**「在一个时间段内只允许一个进程使用的资源」(这也就是互斥的意思),我们将其称为「临界资源」,对临界资源进行访问的那段代码称为「临界区」**。

image-20211104211312345

通俗的对比一下进程互斥和进程同步:

  • 进程同步:进程 A 应在进程 B 之前执行
  • 进程互斥:进程 A 和进程 B 不能在同一时刻执行

从上不难看出,「进程互斥是一种特殊的进程同步」,即逐次使用临界资源,也是对进程使用资源的先后执行次序的一种协调。

2、信号量

信号量,英文为semaphore,最早由荷兰科学家、图灵奖获得者E. W. Dijkstra设计,任何操作系统教科书的“进程同步”部分都会有详细叙述。

Linux的信号量秉承POSIX规范,用man sem_overview可以查看相关信息。本次实验涉及到的信号量系统调用包括:sem_open()sem_wait()sem_post()sem_unlink()

sem_t *sem_open(const char *name, unsigned int value);//创建或打开信号量
int sem_wait(sem_t *sem);//P操作
int sem_post(sem_t *sem);//V操作
int sem_unlink(const char *name);//删除信号量
  • sem_open() 的功能是创建一个信号量,或打开一个已经存在的信号量。

    • sem_t 是信号量类型,根据实现的需要自定义。
    • name 是信号量的名字。不同的进程可以通过提供同样的 name 而共享同一个信号量。如果该信号量不存在,就创建新的名为 name 的信号量;如果存在,就打开已经存在的名为 name 的信号量。
    • value 是信号量的初值,仅当新建信号量时,此参数才有效,其余情况下它被忽略。当成功时,返回值是该信号量的唯一标识(比如,在内核的地址、ID 等),由另两个系统调用使用。如失败,返回值是 NULL
  • sem_wait() 就是信号量的 P 原子操作。如果继续运行的条件不满足,则令调用进程等待在信号量 sem 上。返回 0 表示成功,返回 -1 表示失败。

  • sem_post() 就是信号量的 V 原子操作。如果有等待 sem 的进程,它会唤醒其中的一个。返回 0 表示成功,返回 -1 表示失败。

  • sem_unlink() 的功能是删除名为 name 的信号量。返回 0 表示成功,返回 -1 表示失败。

3、生产者—消费者问题

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多进程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个进程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

生产者—消费者问题的解法几乎在所有操作系统教科书上都有,其基本结构为:

Producer()
{
    // 生产一个产品 item;

    // 空闲缓存资源
    P(Empty);

    // 互斥信号量
    P(Mutex);

    // 将item放到空闲缓存中;
    V(Mutex);

    // 产品资源
    V(Full);
}

Consumer()
{
    P(Full);
    P(Mutex);

    //从缓存区取出一个赋值给item;
    V(Mutex);

    // 消费产品item;
    V(Empty);
}

显然在演示这一过程时需要创建两类进程,一类执行函数 Producer(),另一类执行函数 Consumer()

4、总结

本实验的总体思路就是,分别实现sem_open()、sem_wait()、sem_post()和sem_unlink()系统调用,在pc.c程序中调用4个函数完成PV操作,解决生产者——消费者问题。

四、实验步骤

1、信号量的实现

因为在Linux0.11中没有信号量的定义,所有我们要自己写一个信号量的头文件(也就是库)sem.h,运用数据结构的知识,我们可以写出以下代码:

#ifndef _SEM_H
#define _SEM_H

#include <linux/sched.h>

#define SEMTABLE_LEN    20
#define SEM_NAME_LEN    20

typedef struct semaphore{
    char name[SEM_NAME_LEN];
    int value;
    struct task_struct *queue;
} sem_t;
extern sem_t semtable[SEMTABLE_LEN];

#endif

有了信号量的头文件之后,就可以实现信号量的代码。sem.c

#include <linux/sem.h>
#include <linux/sched.h>
#include <unistd.h>
#include <asm/segment.h>
#include <linux/tty.h>
#include <linux/kernel.h>
#include <linux/fdreg.h>
#include <asm/system.h>
#include <asm/io.h>
//#include <string.h>

sem_t semtable[SEMTABLE_LEN];
//信号量的个数
int cnt = 0;
//创建一个信号量,或打开一个已经存在的信号量
sem_t *sys_sem_open(const char *name,unsigned int value)
{
    char kernelname[100]; 
    int isExist = 0;
    int i=0;
    //信号量名字字符数
    int name_cnt=0;
    //获取信号量名字的字符数
    while( get_fs_byte(name+name_cnt) != '\0')
    name_cnt++;
	//如果字符数大于设定的字符数,返回空
    if(name_cnt>SEM_NAME_LEN)
    return NULL;
    //获得传入的信号量名
    for(i=0;i<name_cnt;i++)
    kernelname[i]=get_fs_byte(name+i);
    int name_len = strlen(kernelname);
    int sem_name_len =0;
    sem_t *p=NULL;
    //如果信号量已存在,找到并打开
    for(i=0;i<cnt;i++)
    {
        sem_name_len = strlen(semtable[i].name);
        if(sem_name_len == name_len)
        {
                if( !strcmp(kernelname,semtable[i].name) )
                {
                    isExist = 1;
                    break;
                }
        }
    }
    if(isExist == 1)
    {
        p=(sem_t*)(&semtable[i]);
        //printk("find previous name!\n");
    }
    //如果信号量不存在,创建,信号量个数+1
    else
    {
        i=0;
        for(i=0;i<name_len;i++)
        {
            semtable[cnt].name[i]=kernelname[i];
        }
        semtable[cnt].value = value;
        p=(sem_t*)(&semtable[cnt]);
         //printk("creat name!\n");
        cnt++;
     }
    return p;
}

/*
cli()函数和sti()函数分别是关中断和开中断操作,保证P操作的原子性
*/

//P操作
int sys_sem_wait(sem_t *sem)
{
    cli();
    //如果信号量的值小于0,则休眠等待
    while( sem->value <= 0 )       
        sleep_on(&(sem->queue));    
    sem->value--;               
    sti();
    return 0;
}
//V操作
int sys_sem_post(sem_t *sem)
{
    cli();
    sem->value++;
    if( (sem->value) <= 1)
        wake_up(&(sem->queue));
    sti();
    return 0;
}
//删除信号量
//sys_sem_open代码同理
int sys_sem_unlink(const char *name)
{
    char kernelname[100];   
    int isExist = 0;
    int i=0;
    int name_cnt=0;
    while( get_fs_byte(name+name_cnt) != '\0')
            name_cnt++;
    if(name_cnt>SEM_NAME_LEN)
            return NULL;
    for(i=0;i<name_cnt;i++)
            kernelname[i]=get_fs_byte(name+i);
    int name_len = strlen(name);
    int sem_name_len =0;
    for(i=0;i<cnt;i++)
    {
        sem_name_len = strlen(semtable[i].name);
        if(sem_name_len == name_len)
        {
                if( !strcmp(kernelname,semtable[i].name))
                {
                        isExist = 1;
                        break;
                }
        }
    }
    if(isExist == 1)
    {
        int tmp=0;
        for(tmp=i;tmp<=cnt;tmp++)
        {
            semtable[tmp]=semtable[tmp+1];
        }
        cnt = cnt-1;
        return 0;
    }
    else
        return -1;
}

将写好的sem.c放在linux-0.11/kernel目录下:

image-20211104225344143

将写好的sem.h放在linux-0.11/include/linux目录下:

img

2、 实现信号量的系统调用

结合上一个实验的操作,很容易实现信号量的系统调用

(1)添加系统调用号

修改linux-0.11/include目录下的unistd.h文件,添加对应的系统调用号。

image-20211104232106061

由于是接着上一个实验,所以系统调用号从74开始。

#define __NR_sem_open 74
#define __NR_sem_wait 75
#define __NR_sem_post 76
#define __NR_sem_unlink 77

image-20211104232252144

(2)修改系统调用总数

修改linux-0.11/kernel目录下的system_call.s文件

image-20211104232826222

修改第61行的代码,改为78。(系统调用号从0开始)

image-20211104233726240

(3)在系统调用函数表中添加新的系统调用函数

修改linux-0.11/include/linux目录下的sys.h文件

image-20211104234205301

在代码末尾处按照相同的格式添加系统调用函数,在系统调用函数表中,也按照相同的顺序添加系统调用函数。

extern int sys_sem_open();
extern int sys_sem_wait();
extern int sys_sem_post();
extern int sys_sem_unlink();
sys_sem_open,sys_sem_wait,sys_sem_post,sys_sem_unlink

image-20211104234516768

(4)修改Makefile

修改linux-0.11/kernel目录下的Makefile文件

image-20211104235345033

修改2处

第1处:在OBJS的末尾处添加sem.o

OBJS  = sched.o system_call.o traps.o asm.o fork.o \         
    panic.o printk.o vsprintf.o sys.o exit.o \         
    signal.o mktime.o who.o sem.o

image-20211104235523778

第2处:在Dependencies中添加相关依赖

sem.s sem.o: sem.c ../include/linux/sem.h ../include/unistd.h

image-20211104235824430

(5)编译内核

进入到linux-0.11目录下,输入make命令,进行编译。

image-20211105000200621

最后一行出现sync,说明编译成功。

image-20211105000405551

(6)挂载文件

oslab目录下使用./mount_hdc命令,运行mount_hdc文件。

image-20211105001030057

unistd.h文件放入到hdc/usr/include目录下

cp ./linux-0.11/include/unistd.h ./hdc/usr/include/

sys.h文件和sem.h文件放入到hdc/usr/include/kernel目录下

cp ./linux-0.11/include/linux/sys.h ./hdc/usr/include/linux/

cp ./linux-0.11/include/linux/sem.h ./hdc/usr/include/linux/

3、解决生产者——消费者问题

(1)编写pc.c

hdc/uer/root目录下新建pc.c文件。代码如下:

#define __LIBRARY__
#include <unistd.h>
#include <linux/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/sched.h>

_syscall2(sem_t *,sem_open,const char *,name,unsigned int,value)
_syscall1(int,sem_wait,sem_t *,sem)
_syscall1(int,sem_post,sem_t *,sem)
_syscall1(int,sem_unlink,const char *,name)

const char *FILENAME = "/usr/root/buffer_file";    /* 消费生产的产品存放的缓冲文件的路径 */
const int NR_CONSUMERS = 5;                        /* 消费者的数量 */
const int NR_ITEMS = 50;                        /* 产品的最大量 */
const int BUFFER_SIZE = 10;                        /* 缓冲区大小,表示可同时存在的产品数量 */
sem_t *metux, *full, *empty;                    /* 3个信号量 */
unsigned int item_pro, item_used;                /* 刚生产的产品号;刚消费的产品号 */
int fi, fo;                                        /* 供生产者写入或消费者读取的缓冲文件的句柄 */


int main(int argc, char *argv[])
{
    char *filename;
    int pid;
    int i;

    filename = argc > 1 ? argv[1] : FILENAME;
    /* O_TRUNC 表示:当文件以只读或只写打开时,若文件存在,则将其长度截为0(即清空文件)
     * 0222 和 0444 分别表示文件只写和只读(前面的0是八进制标识)
     */
    fi = open(filename, O_CREAT| O_TRUNC| O_WRONLY, 0222);    /* 以只写方式打开文件给生产者写入产品编号 */
    fo = open(filename, O_TRUNC| O_RDONLY, 0444);            /* 以只读方式打开文件给消费者读出产品编号 */

    metux = sem_open("METUX", 1);    /* 互斥信号量,防止生产消费同时进行 */
    full = sem_open("FULL", 0);        /* 产品剩余信号量,大于0则可消费 */
    empty = sem_open("EMPTY", BUFFER_SIZE);    /* 空信号量,它与产品剩余信号量此消彼长,大于0时生产者才能继续生产 */

    item_pro = 0;

    if ((pid = fork()))    /* 父进程用来执行消费者动作 */
    {
        printf("pid %d:\tproducer created....\n", pid);
        /* printf()输出的信息会先保存到输出缓冲区,并没有马上输出到标准输出(通常为终端控制台)。
         * 为避免偶然因素的影响,我们每次printf()都调用一下stdio.h中的fflush(stdout)
         * 来确保将输出立刻输出到标准输出。
         */
        fflush(stdout);

        while (item_pro <= NR_ITEMS)    /* 生产完所需产品 */
        {
            sem_wait(empty);
            sem_wait(metux);

            /* 生产完一轮产品(文件缓冲区只能容纳BUFFER_SIZE个产品编号)后
             * 将缓冲文件的位置指针重新定位到文件首部。
             */
            if(!(item_pro % BUFFER_SIZE))
                lseek(fi, 0, 0);

            write(fi, (char *) &item_pro, sizeof(item_pro));        /* 写入产品编号 */
            printf("pid %d:\tproduces item %d\n", pid, item_pro);
            fflush(stdout);
            item_pro++;

            sem_post(full);        /* 唤醒消费者进程 */
            sem_post(metux);
        }
    }
    else    /* 子进程来创建消费者 */
    {
        i = NR_CONSUMERS;
        while(i--)
        {
            if(!(pid=fork()))    /* 创建i个消费者进程 */
            {
                pid = getpid();
                printf("pid %d:\tconsumer %d created....\n", pid, NR_CONSUMERS-i);
                fflush(stdout);

                while(1)
                {
                    sem_wait(full);
                    sem_wait(metux);

                    /* read()读到文件末尾时返回0,将文件的位置指针重新定位到文件首部 */
                    if(!read(fo, (char *)&item_used, sizeof(item_used)))
                    {
                        lseek(fo, 0, 0);
                        read(fo, (char *)&item_used, sizeof(item_used));
                    }

                    printf("pid %d:\tconsumer %d consumes item %d\n", pid, NR_CONSUMERS-i+1, item_used);
                    fflush(stdout);

                    sem_post(empty);    /* 唤醒生产者进程 */
                    sem_post(metux);

                    if(item_used == NR_ITEMS)    /* 如果已经消费完最后一个商品,则结束 */
                        goto OK;
                }
            }
        }
    }
OK:
    close(fi);
    close(fo);
    return 0;
}

image-20211105003800147

(2)编译pc.c

使用./run命令进入到pc.c虚拟机

image-20211105004031807

image-20211105004256617

使用gcc -o pc pc.c命令编译pc.c

编译之后,会出现2个警告,不会对编译产生影响。

image-20211105004840630

(3)查看结果

不知是Linux 0.11还是bochs的bug,如果向终端输出的信息较多,bochs的虚拟屏幕会产生混乱。此时按ctrl+L可以重新初始化一下屏幕,但输出信息一多,还是会混乱。建议把输出信息重定向到一个文件,然后用vi、more等工具按屏查看这个文件,可以基本解决此问题。

使用./pc > out 命令,把结果输入到out文件当中,并使用vi命令查看。

image-20211105010212524

结果

image-20211105010304368

可以看出是从0开始,按照依次顺序正常生产消费。

实验结束。

五、参考文档

  1. 哈尔滨工业大学《操作系统》实验指导书

  2. 操作系统实验六 信号量的实现和应用(哈工大李治军)

  3. 操作系统实验(4: 信号量的实现和应用)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

信号量的实现和应用 的相关文章

  • 一个简单的开源PHP爬虫框架『Phpfetcher』

    这篇文章首发在吹水小镇 xff1a http blog reetsee com archives 366 要在手机或者电脑看到更好的图片或代码欢迎到博文原地址 也欢迎到博文原地址批评指正 转载请注明 xff1a 吹水小镇 reetsee c
  • 「更快!更爽!」吹水新闻2.0

    这篇文章首发在吹水小镇 xff1a http blog reetsee com archives 388 要在手机或者电脑看到更好的图片或代码欢迎到博文原地址 也欢迎到博文原地址批评指正 转载请注明 xff1a 吹水小镇 reetsee c
  • IFS字段分隔符的修改与还原

    IFS字段分隔符 默认包含空格 制表符 换行符查看IFS set grep IFS修改IFS为只换行还原 默认包含空格 制表符 换行符 查看IFS set grep IFS 修改IFS为只换行 OLDIFS 61 IFS IFS 61 n
  • 如何对日志文件进行二分查找?开源文件二分查找工具『timecat』介绍

    这篇文章是我从自己的博客搬运过来的 转载请注明 xff1a 吹水小镇 reetsee com 原文链接地址 xff1a http blog reetsee com archives 502 要获得更好的阅读体验 xff0c 欢迎大家到这篇文
  • 阿里的电话面试是神马感觉

    感觉就是被问了个稀巴烂 xff0c 估计到不了下一轮 问了神马呢 xff0c 问了我的项目 xff0c 我描述了一阵子之后 xff0c 当他问到使用人数的时候 xff0c 我说是内部使用 没有发布 xff0c 只是一件比赛的作品的时候 xf
  • 找出带环单向链表的环入口(交点)

    其实这个问题已经被问烂了 xff0c 但是之前没有想透 xff0c 今天算是解决得差不多 找环的入口这个问题 xff0c 其实是建立在另外一个问题之上的 判断单向链表是否有环 土方法很多 xff0c 但是比较好的目前就那么一个 xff1a
  • 关于我最近看的一本书——大名鼎鼎的APUE

    APUE xff0c Know as Unix环境高级编程 xff0c 我每天都在用自己的绳命去看 xff0c 每天都燃烧自己去看 什么样的书 xff0c 一看就是上乘之中的珍稀之品 xff1f 这本 不同于不少机械工业出版社的大部头 xf
  • 复杂网络笔记-R语言

    最近学习了下复杂网络相关的东西 xff0c 总结了部分基础的理论 xff0c 与使用R语言igraph包 xff0c 总结如下 xff0c 还需要继续深入 xff0c 目前只学了皮毛 复杂网络的复杂性 1 结构复杂性 网络连接结构看上去错综
  • 写博客加分不

    写第一篇博客 xff0c 就看看加分不 xff0c 这个网站分很重要 xff0c 不然下不了东西
  • 谨以此文献给才毕业一两年的朋友

    谨以此文献给才毕业一两年的朋友 选自同事信件 谨以此文献给才毕业一两年的朋友我们终于进入了这个社会 从此结束了被学校老师看管的生涯 xff0c 结束了做父母乖宝贝的日子 xff0c 也结束从父母兄长那里拿钱的幸福时光 我们从家里搬了出来 x
  • layui实际项目使用过程中遇到的兼容性问题

    layui实践兼容 本文记录自己在layui的实际使用过程中遇到的一些兼容性问题 xff0c 烂笔头 gt gt gt 大脑 layui在vue项目中不能自动渲染的问题 下载layui源码到本地 xff0c 在vue的项目中引用 xff0c
  • Vue从零开始01——Vue双向绑定原理和MVVM

    Vue是一个主张较弱的渐进式框架 xff0c 什么是主张弱的渐进式框架呢 xff1f 主张弱和渐进式说的都是 xff0c 可以灵活的选取你需要用的东西和不需要用的东西 xff0c 需要用的就引进来 xff0c 不需要用的就不引 xff0c
  • layui数据表格复选框自动选中部分选项

    layui数据表格复选框自动选中部分选项 layui官方文档中给出了数据表格复选框全选字段 xff0c 如下 xff0c 但是部分选中没有配置项 部分选中的方法 xff1a 利用异步数据接口的参数 xff1a 实现代码 xff1a layu
  • Js代码收藏大全

    1 nc ntextmenu 61 34 window event returnvalue 61 false 34 将彻底屏蔽鼠标右键 lt table border nc ntextmenu 61 return false gt lt t
  • jenkins(linux)远程构建windows项目(超详细)

    1 背景 公司开发的资产管理探针需要在linux unix windows上分别部署 xff0c 使用自动化构建jenkins工具远程部署linux平台容易实现 xff0c windows比较折腾 xff0c 现将个人经验分享 2 环境 I
  • jq实现点击一个按钮,触发另一个点击事件

    jq实现点击一个按钮 xff0c 触发另一个点击事件 span class token function span span class token punctuation span span class token string 34 a
  • Promise.race()异步超时处理

    span class token keyword function span span class token function timeoutPromise span span class token punctuation span p
  • Ztree点击树节点选中前面的复选框

    span class token keyword var span setting span class token operator 61 span span class token punctuation span check span
  • 关于vue-cli项目搭建完成之后的yarn serve启动指令

    在项目的配置文件package json里面 xff0c 声明了两条指令的快捷指令 xff0c 当执行yarn serve的时候就相当于执行了vue cli service serve这条指令

随机推荐

  • Vue项目url中的BASE_URL解析

    在Vue中遇到很多url都用到了 lt 61 BASE URL gt 这个东西 span class token operator lt span span class token operator 61 span span class t
  • VSCode 中 js 文件类型注释报错的问题解决

    在阅读 Vue js 源码的时候遇见的一个问题 xff0c 本来可以忽略的玩意 xff0c 但是报错的波浪线 xff0c 如鲠在喉实在受不了 xff0c 解决之 报错说明 Type annotations can only be used
  • 3-3-07-nuxtjs案例realworld-nuxtjs

    realworld nuxtjs 项目地址 xff1a https gitee com dingxd9702 realworld nuxtjs 创建项目 mkdir realworld nuxtjsyarn init yyarn add n
  • 3-4-01-搭建自己的 Server Side Render

    搭建自己的 Server Side Render Vue 实例的服务端渲染 使用 vue server renderer 插件完成 vue 实例的服务端渲染使用 express 创建一个 node 服务器fs 模块读取 html 模板 sp
  • css实现超出部分显示省略号

    显示一行 xff0c 省略号 white span class token operator span space span class token punctuation span nowrap span class token punc
  • 正则表达式 匹配字符串 matches()方法的运用

    package cn niit demo5zhengze 正则表达式 匹配字符串 matches 方法的运用 public class Test public static void main String args checkQQ che
  • Blazor组件自做三 : 使用JS隔离封装ZXing扫码

    本文基础步骤参考前两篇文章 Blazor组件自做一 使用JS隔离封装viewerjs库 Blazor组件自做二 使用JS隔离制作手写签名组件 1 在文件夹wwwroot lib 添加zxing子文件夹 里面下载库文件 文件文末源码里可复制
  • zabbix报警方式,邮件报警和微信报警。

    整理csdn时候发现了一篇2016年整理zabbix报警不知道当时啥情况没有发布出去 xff0c 凑个数重新发布 最近这些天都在弄Zabbix不再只是简单的监控物理硬件 xff0c 服务端口 xff0c 流量图等 让Zabbix的功能发挥到
  • itk registration 1

    参考 xff1a https itk org ITKSoftwareGuide html Book2 ITKSoftwareGuide Book2ch3 html x26 1740003 18 图像配准是确定将一幅图像上的点映射到另一幅图像
  • 凤凰涅槃

    涅槃 xff0c 涅槃 xff0c 太阳般灿烂 xff0c 黑色的凤凰飞入烈火 xff0c 一切腐朽烧干 涅槃 xff0c 涅槃 xff0c 大海般坦然 xff0c 红色的凤凰浴火重生 xff0c 一切希望复燃 涅槃 xff0c 涅槃 xf
  • Qt单个模块的编译

    有可能使用的Qt没有自己需要的模块 xff0c 这个时候就只能自己去编译这个模 本次就拿QWebEngine这个模块来讲 xff0c 这个模块比较复杂 xff0c 编译很容易出问题
  • H5/js/web lottie解析json 播放视频或动画

    H5 js web lottie解析json 播放视频或动画 1 需要UI设计的小伙伴用AE把效果图导出时导成JSON格式 xff0c 并且把json引入到你需要用到的项目里面2 在index html中引入lottie min js3 查
  • VirtualBox虚拟机闪退后如何重新打开

    电脑 xff08 Mac系统 xff09 由于某种问题自动重启了 xff0c 重启后之前在用的VirtualBox xff08 虚拟机管理器 xff09 能恢复运行 xff0c 但VirtualBoxVM xff08 虚拟机终端 xff09
  • (五)建筑物多边形化简系列——最小外接矩形的获取

    最小外接矩形问题是在给出一个多边形 xff08 或一群点 xff09 xff0c 求出面积最小且外接多边形的矩形的问题 这个问题看起来并不难 xff0c 但是具体实现并不简单 除了调用现有的公开库之外 xff0c 这里给出一种简单且易理解的
  • 使用 LxRunOffline 实现 WSL 自定义安装、备份

    本文初衷是想在非默认目录下安装WSL xff0c 查阅资料后用LxRunOffline了解并实现了WSL自定义安装 前言 虽说目前的 WSL 在 Windows 10 生态中已经越发成熟 xff0c 但在实际使用中依旧存在一些不足之处 xf
  • [Python基础] @statismethod和@classmethod中的注意事项

    声明 xff1a 以下讨论将区别方法 xff08 method xff09 和函数 xff08 function xff09 两个概念 xff0c 方法依托于对象 xff0c 而函数可以脱离对象之外存在 众所周知 xff0c python类
  • centos安装chrome 无法运行 Running as root without --no-sandbox is not supported ygote_host_impl_linux.cc

    launcher Failed to get the debug url 0313 145713 562233 ERROR zygote host impl linux cc 90 Running as root without no sa
  • Blazor组件自做七: 使用JS隔离制作定位/持续定位组件

    1 运行截图 演示地址 2 在文件夹wwwroot lib 添加geolocation子文件夹 添加geolocation js文件 本组件主要是调用浏览器两个API实现基于浏览器的定位功能 现代桌面和移动端都支持 包括MAUI Blazo
  • 普通表转换分区表操作步骤

    普通表转换分区表操作步骤 1 转分区表原因 生产数据库 xff0c 一张表 xff0c 一亿多行数据 xff0c 绝大部分查询按月为维度做时间范围查询 xff0c 未分区状态下 xff0c 查询IO量大 xff0c 计划以分区截剪的方式减少
  • 信号量的实现和应用

    信号量的实现和应用 一 实验环境 本次实验的操作环境还是一样的实验环境 环境文件如下 xff1a 如果不清楚的话请参考往期博客 二 实验目标与内容 1 目标 xff1a 加深对进程同步与互斥概念的认识 xff1b 掌握信号量的使用 xff0