浅析Linux内核进程间通信(信号量)

news2024/9/26 5:13:56

信号灯与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源(临界区,类似于互斥锁),同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。

一,信号灯概述

信号灯与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源(临界区,类似于互斥锁),同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。

信号灯有以下两种类型:

  • 二值信号灯:最简单的信号灯形式,信号灯的值只能取0或1,类似于互斥锁。
    注:二值信号灯能够实现互斥锁的功能,但两者的关注内容不同。信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
  • 计算信号灯:信号灯的值可以取任意非负值(当然受内核本身的约束)。

二,Linux信号灯

linux对信号灯的支持状况与消息队列一样,在red had 8.0发行版本中支持的是系统V的信号灯。因此,本文将主要介绍系统V信号灯及其相应API。在没有声明的情况下,以下讨论中指的都是系统V信号灯。

注意,通常所说的系统V信号灯指的是计数信号灯集。

三,信号灯与内核

1、系统V信号灯是随内核持续的,只有在内核重起或者显示删除一个信号灯集时,该信号灯集才会真正被删除。因此系统中记录信号灯的数据结构(struct ipc_ids sem_ids)位于内核中,系统中的所有信号灯都可以在结构sem_ids中找到访问入口。

2、下图说明了内核与信号灯是怎样建立起联系的:

其中:structipc_ids sem_ids是内核中记录信号灯的全局数据结构;描述一个具体的信号灯及其相关信息。

其中,struct sem结构如下:

struct sem
{    
  int semval; // current value    
  int sempid; // pid of last operation
}

从上图可以看出,全局数据结构struct ipc_ids sem_ids可以访问到struct ipc_id ipcid的一个成员:struct kern_ipc_perm;而每个struct kern_ipc_perm能够与具体的信号灯集对应起来是因为在该结构中,有一个key_t类型成员key,而key则唯一确定一个信号灯集struct sem_array;同时,结构struct sem_array的最后一个成员sem_nsems确定了该信号灯在信号灯集中的顺序,这样内核就能够记录每个信号灯的信息了。

kern_ipc_perm结构如下:

//内核中全局数据结构sem_ids能够访问到该结构;
struct kern_ipc_perm
{
    key_t   key;    //该键值则唯一对应一个信号灯集
    uid_t   uid;
    gid_t   gid;
    uid_t   cuid;
    gid_t   cgid;
    mode_t  mode;
    unsigned long seq;
}
/*系统中的每个信号灯集对应一个sem_array 结构 */
struct sem_array 
{
    struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */
    time_t sem_otime; /* last semop time */
    time_t sem_ctime; /* last change time */
    struct sem *sem_base; /* ptr to first semaphore in array */
    struct sem_queue *sem_pending; /* pending operations to be processed */
    struct sem_queue **sem_pending_last; /* last pending operation */
    struct sem_undo *undo; /* undo requests on this array */
    unsigned long sem_nsems; /* no. of semaphores in array */
};

其中,sem_queue结构如下:

/* 系统中每个因为信号灯而睡眠的进程,都对应一个sem_queue结构*/
struct sem_queue 
{
    struct sem_queue * next; /* next entry in the queue */
    struct sem_queue ** prev; /* previous entry in the queue, *(q->prev) == q */
    struct task_struct* sleeper; /* this process */
    struct sem_undo * undo; /* undo structure */
    int pid; /* process id of requesting process */
    int status; /* completion status of operation */
    struct sem_array * sma; /* semaphore array for operations */
    int id; /* internal sem id */
    struct sembuf * sops; /* array of pending operations */
    int nsops; /* number of operations */
    int alter; /* operation will alter semaphore */
};

四,操作信号灯

对信号灯的操作无非有下面三种类型:

1、打开或创建信号灯 与消息队列的创建及打开基本相同,不再详述。

2、信号灯值操作 linux可以增加或减小信号灯的值,相应于对共享资源的释放和占有。具体参见后面的semop系统调用。

3、获得或设置信号灯属性: 系统中的每一个信号灯集都对应一个struct sem_array结构,该结构记录了信号灯集的各种信息,存在于系统空间。为了设置、获得该信号灯集的各种信息及属性,在用户空间有一个重要的联合结构与之对应,即union semun。

联合semun数据结构:

union semun   
{  
    int val; /* value for SETVAL */  
    struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */  
    unsigned short *array; /* array for GETALL & SETALL */  
    struct seminfo *__buf; /* buffer for IPC_INFO */ //test!!  
    void *__pad;  
};  
 
The semid_ds data structure is defined in <sys/sem.h> as follows:
 
struct semid_ds {
    struct ipc_perm sem_perm;  /* Ownership and permissions */
    time_t          sem_otime; /* Last semop time */
    time_t          sem_ctime; /* Last change time */
    unsigned short  sem_nsems; /* No. of semaphores in set */
};
 
The ipc_perm structure is defined in <sys/ipc.h> as follows (the highlighted fields are settable using IPC_SET):
struct ipc_perm {
    key_t key;            /* Key supplied to semget() */
    uid_t uid;            /* Effective UID of owner */
    gid_t gid;            /* Effective GID of owner */
    uid_t cuid;           /* Effective UID of creator */
    gid_t cgid;           /* Effective GID of creator */
    unsigned short mode;  /* Permissions */
    unsigned short seq;   /* Sequence number */
};
 
seminfo, defined in <sys/sem.h> if the _GNU_SOURCE feature test macro is defined:
 
struct  seminfo {
    int semmap;  /* #(number?) of entries in semaphore map;
                                unused */
    int semmni;  /* Max. # of semaphore sets */
    int semmns;  /* Max. # of semaphores in all
                                semaphore sets */
    int semmnu;  /* System-wide max. # of undo
                                structures; unused */
    int semmsl;  /* Max. # of semaphores in a set */
    int semopm;  /* Max. # of operations for semop() */
    int semume;  /* Max. # of undo entries per
                                process; unused */
    int semusz;  /* size of struct sem_undo */
    int semvmx;  /* Maximum semaphore value */
    int semaem;  /* Max. value that can be recorded for
                                semaphore adjustment (SEM_UNDO) */
};
The semmsl, semmns, semopm, and semmni settings can be changed via /proc/sys/kernel/sem; see proc(5) for details.


五,信号灯API

1、文件名到键值

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok (char*pathname, char proj);

它返回与路径pathname相对应的一个键值。

2、 linux特有的ipc()调用:

int ipc(unsigned int call, int first, intsecond, int third, void *ptr, long fifth);

参数call取不同值时,对应信号灯的三个系统调用: 当call为SEMOP时,对应int semop(int semid, struct sembuf *sops, unsigned nsops)调用; 当call为SEMGET时,对应int semget(key_t key, int nsems,int semflg)调用; 当call为SEMCTL时,对应int semctl(int semid,int semnum,int cmd,union semun arg)调用; 这些调用将在后面阐述。

3、系统V信号灯API

系统V消息队列API只有三个,使用时需要包括几个头文件:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

1)int semget(key_t key, int nsems, int semflg) 参数key是一个键值,由ftok获得,唯一标识一个信号灯集,用法与msgget()中的key相同;参数nsems指定打开或者新创建的信号灯集中将包含信号灯的数目;semflg参数是一些标志位。参数key和semflg的取值,以及何时打开已有信号灯集或者创建一个新的信号灯集与msgget()中的对应部分相同。该调用返回与健值key相对应的信号灯集描述字。 调用返回:成功返回信号灯集描述字,否则返回-1。 注:如果key所代表的信号灯已经存在,且semflg指定了IPC_CREAT|IPC_EXCL标志,那么即使参数nsems与原来信号灯的数目不等,返回的也是EEXIST错误;如果semflg只指定了IPC_CREAT标志,那么参数nsems必须与原来的值一致,在后面程序实例中还要进一步说明。

Upon creation, the least significant 9 bits of the argument semflg 
define the permissions (for owner, group and others) for 
the semaphore set. These bits have the same format, and the same meaning,
 as the mode argument of open(2) (though the execute permissions are not 
meaningful for semaphores, and write permissions mean permission to alter semaphore values)

例如:IPC_CREAT | 权限标识

2)int semop(int semid, struct sembuf *sops, unsigned nsops); semid是信号灯集ID,sops指向数组的每一个sembuf结构都刻画一个在特定信号灯上的操作。nsops为sops指向数组的大小。 sembuf结构如下:

struct sembuf
{    
  unsigned short sem_num; /* semaphore index in array */    
  short sem_op; /* semaphore operation */    
  short sem_flg; /* operation flags */
};


sem_num对应信号集中的信号灯,其值是一个从0到相应的信号量集的资源总数(ipc_perm.sem_nsems)之间的整数,0对应第一个信号灯。

资料直通车:最新Linux内核源码资料文档+视频资料

内核学习地址:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

sem_flg可取IPC_NOWAIT以及SEM_UNDO两个标志。如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。如果设置了该标志位,那么在进程没有释放共享资源就退出时,内核将代为释放。如果为一个信号灯设置了该标志,内核都要分配一个sem_undo结构来记录它,为的是确保以后资源能够安全释放。事实上,如果进程退出了,那么它所占用的资源就释放了,但信号灯值却没有改变,此时,信号灯值反映的已经不是资源占有的实际情况,在这种情况下,问题的解决就靠内核来完成。这有点像僵尸进程,进程虽然退出了,资源也都释放了,但内核进程表中仍然有它的记录,此时就需要父进程调用waitpid来解决问题了。

sem_op的值大于0,等于0以及小于0确定了对sem_num指定的信号灯进行的三种操作。具体请参考linux相应手册页。 (是信号量在一次操作中需要改变的数值(可以是非1的数值)。通常只会用到两个值:-1----P操作,申请资源,如果已经没有资源可申请,则阻塞。为阻塞原语;1---V操作,释放资源,为唤醒原语。)

也许从实际含义上更好理解这些操作:信号灯的当前值记录相应资源目前可用数目;sem_op>0对应相应进程要释放sem_op数目的共享资源;sem_op=0可以用于对共享资源是否已用完的测试;sem_op<0相当于进程要申请-sem_op个共享资源。再联想操作的原子性,更不难理解该系统调用何时正常返回,何时睡眠等待。调用返回:成功返回0,否则返回-1。

若sem_op 是正数,其值就加到semval上,即释放信号量控制的资源若sem_op 是0,那么调用者希望等到semval变为0,如果semval是0就返回;若sem_op 是负数,那么调用者希望等待到semval变为大于或等于sem_op的绝对值。如果当前semval大于或等于sem_op的绝对值,则立即返回。

这里需要强调的是semop同时操作多个信号灯,在实际应用中,对应多种资源的申请或释放。semop保证操作的原子性,这一点尤为重要。尤其对于多种资源的申请来说,要么一次性获得所有资源,要么放弃申请,要么在不占有任何资源情况下继续等待,这样,一方面避免了资源的浪费;另一方面,避免了进程之间由于申请共享资源造成死锁。

3) int semctl(int semid,int semnum,int cmd,union semun arg) 该系统调用实现对信号灯的各种控制操作,参数semid指定信号灯集,参数cmd指定具体的操作类型;参数semnum指定对哪个信号灯操作,只对几个特殊的cmd操作有意义;arg用于设置或返回信号灯信息。该系统调用详细信息请参见其手册页,这里只给出参数cmd所能指定的操作。

IPC_RMID Immediately remove the semaphore set, awakening all processes blocked in semop()
calls on the set (with an error return and errno set to EIDRM). The effective user ID
of the calling process must match the creator or owner of the semaphore set, or the
caller must be privileged. The argument semnum is ignored.

调用返回:调用失败返回-1,成功返回与cmd相关:

5. 信号灯的限制

1、一次系统调用semop可同时操作的信号灯数目SEMOPM,semop中的参数nsops如果超过了这个数目,将返回E2BIG错误。SEMOPM的大小特定与系统,redhat 8.0为32。

2、信号灯的最大值:SEMVMX,当设置信号灯值超过这个限制时,会返回ERANGE错误。在redhat 8.0中该值为32767。

3、系统范围内信号灯集的最大数目SEMMNI以及系统范围内信号灯的最大数目SEMMNS。超过这两个限制将返回ENOSPC错误。redhat 8.0中该值为32000。

4、每个信号灯集中的最大信号灯数目SEMMSL,redhat 8.0中为250。 SEMOPM以及SEMVMX是使用semop调用时应该注意的;SEMMNI以及SEMMNS是调用semget时应该注意的。SEMVMX同时也是semctl调用应该注意的。

6. 竞争问题

第一个创建信号灯的进程同时也初始化信号灯,这样,系统调用semget包含了两个步骤:创建信号灯;初始化信号灯。由此可能导致一种竞争状态:第一个创建信号灯的进程在初始化信号灯时,第二个进程又调用semget,并且发现信号灯已经存在,此时,第二个进程必须具有判断是否有进程正在对信号灯进行初始化的能力。在参考文献[1]中,给出了绕过这种竞争状态的方法:当semget创建一个新的信号灯时,信号灯结构semid_ds的sem_otime成员初始化后的值为0。因此,第二个进程在成功调用semget后,可再次以IPC_STAT命令调用semctl,等待sem_otime变为非0值,此时可判断该信号灯已经初始化完毕。下图描述了竞争状态产生及解决方法:

实际上,这种解决方法也是基于这样一个假定:第一个创建信号灯的进程必须调用semop,这样sem_otime才能变为非零值。另外,因为第一个进程可能不调用semop,或者semop操作需要很长时间,第二个进程可能无限期等待下去,或者等待很长时间。

7. 信号灯应用实例

本实例有两个目的:1、获取各种信号灯信息;2、利用信号灯实现共享资源的申请和释放。并在程序中给出了详细注释。

#include <stdio.h>
#include <sys/sem.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
 
 
#define SEM_PATH "/unix/my_sem"
#define max_tries 10
 
#ifndef IPC_INFO
#define IPC_INFO 3     /* see ipcs */
#endif
 
int gi_semid;
const int gi_semcnt = 1;
 
union semun     
{    
    int val;                /* value for SETVAL */    
    struct semid_ds *buf;   /* buffer for IPC_STAT & IPC_SET */    
    unsigned short *array;  /* array for GETALL & SETALL */    
    struct seminfo *__buf;  /* buffer for IPC_INFO */ //test!!    
    void *__pad;    
};
 
 
int main()
{
    int i_flag_create, i_flag_get, i_key, i, i_init_ok, i_tmperrno;
    struct semid_ds semid_ds_var;
    struct seminfo seminfo_var;
    union semun semun_arg;
    struct sembuf sembuf_ask, sembuf_free;
 
    i_flag_create = IPC_CREAT | IPC_EXCL | 00666;
    i_flag_get = IPC_CREAT | 00666;
    i_key = ftok( SEM_PATH, 'a' );
 
    //error handling for ftok here;
    i_init_ok = 0;
    gi_semid = semget( i_key, gi_semcnt, i_flag_create );
 
    //create a semaphore set that only includes one semaphore.
    if( gi_semid < 0 )
    {
        i_tmperrno = errno;
 
        perror( "semget" );
 
        if( i_tmperrno == EEXIST )
        //errno is undefined after a successful library call( including perror call)
        //so it is saved in i_tmperrno.
        {
            gi_semid = semget( i_key, gi_semcnt, i_flag_get );
            // i_flag_get 只包含了IPC_CREAT标志, 参数nsems(这里为1)
            // 必须与原来的信号灯数目一致
 
            semun_arg.buf = &semid_ds_var;
            for ( i = 0; i < max_tries; i++ )
            {
                printf( "semctl IPC_STAT, to solve compete: %d \n", i );
                if ( semctl( gi_semid, 0, IPC_STAT, semun_arg ) == -1 )
                {
                    perror( "semctl error" );
                    i = max_tries;
                }
                else
                {
                    if( semun_arg.buf->sem_otime != 0 )
                    // the create process already initialized the semaphore
                    {
                        i = max_tries; 
                        i_init_ok = 1;
                    }
                    else 
                        sleep(1);
                }
            } // for(i=0; i<max_tries; i++)
 
            if( !i_init_ok )
            // do some initializing, here we assume that the first process that creates the sem
            // will finish initialize the sem and run semop in max_tries*1 seconds. else it will
            // not run semop any more.
            {
                printf( "semctl SETVAL\n" );
                semun_arg.val = 1;
                if ( semctl( gi_semid, 0, SETVAL, semun_arg ) == -1 ) 
                    perror( "semctl SETVAL error" );
            } // if(!i_init_ok)
 
        } // if(i_tmperrno==EEXIST)
        else
        {
            perror("semget error, process exit"); 
            exit( 1 ); 
        }
 
    }
    else //gi_semid>=0; do some initializing
    {        
        printf( "create semaphore success, initialize it\n" );
        semun_arg.val = 1;
        if ( semctl( gi_semid, 0, SETVAL, semun_arg ) == -1 )
            perror( "semctl SETVAL error" );
    }
 
    //get some information about the semaphore and the limit of semaphore in redhat8.0
    semun_arg.buf=&semid_ds_var;
    if( semctl( gi_semid, 0, IPC_STAT, semun_arg ) == -1 )
        perror("semctl IPC STAT");
 
    printf( "owner's uid is %d\n", semun_arg.buf->sem_perm.uid );
    printf( "owner's gid is %d\n", semun_arg.buf->sem_perm.gid );
    printf( "creater's uid is %d\n", semun_arg.buf->sem_perm.cuid );
    printf( "creater's gid is %d\n", semun_arg.buf->sem_perm.cgid );
    
    semun_arg.__buf = &seminfo_var;
    if( semctl( gi_semid, 0, IPC_INFO, semun_arg ) == -1 )
        perror( "semctl IPC_INFO" );
 
    printf( "the number of entries in semaphore map is %d \n", semun_arg.__buf->semmap );
    printf( "max number of semaphore identifiers is %d \n", semun_arg.__buf->semmni );
    printf( "mas number of semaphores in system is %d \n", semun_arg.__buf->semmns );
    printf( "the number of undo structures system wide is %d \n", semun_arg.__buf->semmnu );
    printf( "max number of semaphores per gi_semid is %d \n", semun_arg.__buf->semmsl );
    printf( "max number of ops per semop call is %d \n", semun_arg.__buf->semopm );
    printf( "max number of undo entries per process is %d \n", semun_arg.__buf->semume );
    printf( "the sizeof of struct sem_undo is %d \n", semun_arg.__buf->semusz );
    printf( "the maximum semaphore value is %d \n", semun_arg.__buf->semvmx );
 
    // print sem_otime
    semun_arg.buf = &semid_ds_var;
    if ( semctl( gi_semid, 0, IPC_STAT, semun_arg ) == -1 )
    {
        perror( "semctl error" );
        i = max_tries;
    }
    else
    {
        printf( "before semop, semun_arg.buf->sem_otime: %lu\n", semun_arg.buf->sem_otime );
    }
    
    //now ask for available resource:
    printf( "now ask the resource\n" );
    sembuf_ask.sem_num = 0;
    sembuf_ask.sem_op = -1;
    sembuf_ask.sem_flg = SEM_UNDO;
    if( semop( gi_semid, &sembuf_ask, 1 ) == -1 )   //ask for resource
        perror("semop error");
    printf( "ask the resource success\n" );
 
    // print sem_otime
    semun_arg.buf = &semid_ds_var;
    if ( semctl( gi_semid, 0, IPC_STAT, semun_arg ) == -1 )
    {
        perror( "semctl error" );
        i = max_tries;
    }
    else
    {
        printf( "after semop, semun_arg.buf->sem_otime: %lu\n", semun_arg.buf->sem_otime );
    }
    
    sleep(5);
 
    //do some handling on the sharing resource here, just sleep on it 3 seconds
    printf( "now free the resource\n" );
 
    //now free resource
    sembuf_free.sem_num = 0;
    sembuf_free.sem_op = 1;
    sembuf_free.sem_flg = SEM_UNDO;
 
    if ( semop( gi_semid, &sembuf_free, 1 ) == -1 )     //free the resource.
        if( errno == EIDRM )
            printf( "the semaphore set has been removed\n" );
    
    //you can comment out the codes below to compile a different version:
    if( semctl( gi_semid, 0, IPC_RMID ) == -1 )
        perror( "semctl IPC_RMID" );
    else 
        printf("remove sem ok\n");
 
    return 0;
}

注:读者可以尝试一下注释掉初始化步骤,进程在运行时会出现何种情况(进程在申请资源时会睡眠),同时可以像程序结尾给出的注释那样,把该程序编译成两个不同版本。下面是本程序的运行结果(操作系统gentoo):

create semaphore success, initialize it
owner's uid is 1007
owner's gid is 1007
creater's uid is 1007
creater's gid is 1007
the number of entries in semaphore map is 32000 
max number of semaphore identifiers is 128 
mas number of semaphores in system is 32000 
the number of undo structures system wide is 32000 
max number of semaphores per gi_semid is 250 
max number of ops per semop call is 32 
max number of undo entries per process is 32 
the sizeof of struct sem_undo is 20 
the maximum semaphore value is 32767 
before semop, semun_arg.buf->sem_otime: 0
now ask the resource
ask the resource success
after semop, semun_arg.buf->sem_otime: 1398501342
now free the resource
remove sem ok

Summary:信号灯与其它进程间通信方式有所不同,它主要用于进程间同步。通常所说的系统V信号灯实际上是一个信号灯的集合,可用于多种共享资源的进程间同步。每个信号灯都有一个值,可以用来表示当前该信号灯代表的共享资源可用(available)数量,如果一个进程要申请共享资源,那么就从信号灯值中减去要申请的数目,如果当前没有足够的可用资源,进程可以睡眠等待,也可以立即返回。当进程要申请多种共享资源时,linux可以保证操作的原子性,即要么申请到所有的共享资源,要么放弃所有资源,这样能够保证多个进程不会造成互锁。Linux对信号灯有各种各样的限制,程序中给出了输出结果。另外,如果读者想对信号灯作进一步的理解,建议阅读sem.h源代码,该文件不长,但给出了信号灯相关的重要数据结构。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/371865.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FreeRTOS任务基础知识

单任务和多任务系统单任务系统单任务系统的编程方式&#xff0c;即裸机的编程方式&#xff0c;这种编程方式的框架一般都是在main&#xff08;&#xff09;函数中使用一个大循环&#xff0c;在循环中顺序的执行相应的函数以处理相应的事务&#xff0c;这个大循环的部分可以视为…

Linux内核共享内存使用常见陷阱与分析

所谓共享内存就是使得多个进程可以访问同一块内存空间&#xff0c;是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制&#xff0c;如 信号量结合使用&#xff0c;来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的…

【华为OD机试模拟题】用 C++ 实现 - 最小叶子节点(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 获得完美走位(2023.Q1) 文章目录 最近更新的博客使用说明最小叶子节点题目输入输出示例一输入输出示例二输入输出Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华…

oracle数据库使用JDBC导入ClickHouse数据

一、背景 需求要把oracle中的数据导入到clickhouse中&#xff0c;使用clickhouse的jdbc表引擎&#xff0c;把oracle11g的数据导入到clickhouse中。 二、方案 通过clickhouse-jdbc-bridge&#xff1a;是clickhouse提供的一个jdbc组件&#xff0c;用于通过JDBC的方式远程访问其他…

[面试直通版]网络协议面试核心之IP,TCP,UDP-TCP与UDP协议的区别

点击->计算机网络复习的文章集<-点击 目录 前言 UDP TCP 区别小总结 前言 TCP和UDP都是在传输层&#xff0c;在程序之间传输数据传输层OSI模型&#xff1a;第四层TCP/IP模型&#xff1a;第三层关键协议&#xff1a;TCP协议、UDP协议传输层属于主机间不同进程的通信传…

Unity Lighting -- 光照入门

识别光源 首先来看一张图&#xff0c;看看我们能在这个场景中找到几个光源。 相信大家能够很容易看出来&#xff0c;四盏路灯模型带有四个光源&#xff0c;右边的红绿蓝三个发光的灯也是光源。场景中还有一个光源&#xff0c;这个光源来自天空&#xff0c;让场景看起来有点日落…

尚医通(二十四)就医提醒和预约统计

目录一、就医提醒1、搭建定时任务模块二、后台管理系统-预约统计功能1、开发每天预约数据接口2、封装远程调用接口4、整合统计功能前端一、就医提醒 我们通过定时任务&#xff0c;每天8点执行&#xff0c;提醒就诊 1、搭建定时任务模块 &#xff08;1&#xff09;添加依赖 &l…

【MySQL】调控 字符集

一、 MySQL 启动选项 & 系统变量 启动选项 是在程序启动时我们程序员传递的一些参数&#xff0c;而 系统变量 是影响服务器程序运行行为的变量 1.1 启动项 MySQL 客户端设置项包括&#xff1a; 允许连入的客户端数量 、 客户端与服务器的通信方式 、 表的默认存储引擎 、…

zookeeper入门到精通

文章目录一、zookeeper入门1. 概述zookeeper的工作机制2.特点3.数据结构4.应用场景4.1.统一命名服务4.2.统一配置管理4.3.统一集群管理4.4.服务器节点动态上下线4.5.软负载均衡5.下载地址二、zookeeper安装1.本地模式安装2.配置参数解读三、zookeeper集群操作1.集群操作1.1 集群…

C++学习笔记-继承

继承的基本概念 类与类之间的关系 has-A&#xff0c;包含关系&#xff0c;用以描述一个类由多个“部件类”构成&#xff0c;实现has-A关系用类的成员属性表示&#xff0c;即一个类的成员属性是另一个已经定义好的类。 use-A&#xff0c;一个类使用另一个类&#xff0c;通过类…

前端面试题整理6-react

React 中 keys 的作用是什么&#xff1f; Keys是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识 在开发过程中&#xff0c;我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中React 会借助元素的 Key 值来判断该元素是新近创建的还…

第五章 Opencv图像的几何变换

目录1.缩放图像1-1.resize()方法2.翻转图像2-1.flip()方法3.仿射变换图像3-1.warpAffine()方法3-2.平移3-3.旋转3-4.倾斜4.透视图像4-1.warpPerspective()方法几何变换是指改变图像的几何结构&#xff0c;例如大小、角度和形状等&#xff0c;从而使图像呈现出缩放、翻转、仿射和…

KUKA机器人外部自动运行模式的相关信号配置

KUKA机器人外部自动运行模式的相关信号配置 通过例如PLC这样的控制器来进行外部自动运行控制时,运行接口向机器人控制系统发出机器人进程的相关信号(例如运行许可、故障确认、程序启动等),机器人向上级控制系统发送有关运行状态和故障状态的信息。 必需的配置:  配置CEL…

Oracle-01-简介篇

&#x1f3c6;一、Oracle的历史和发展 Oracle公司成立于1977年&#xff0c;由拉里埃里森&#xff08;Larry Ellison&#xff09;、鲍勃明特&#xff08;Bob Miner&#xff09;和埃德奥茨&#xff08;Ed Oates&#xff09;共同创立。起初&#xff0c;公司的主要业务是开发和销售…

docker基础用法及镜像和容器的常用命令大全

1.docker 体系架构 Docker 采用了 C / S 架构&#xff0c;包括客户端和服务端。Docker 守护进程作为服务端接受来自客户端的请求&#xff0c;并处理这些请求&#xff08;创建、运行、分发容器&#xff09;。客户端和服务端既可以运行在一个机器上&#xff0c;也可通过 socket 或…

数字IC手撕代码--乐鑫科技(次小值与次小值出现的次数)

前言&#xff1a;本专栏旨在记录高频笔面试手撕代码题&#xff0c;以备数字前端秋招&#xff0c;本专栏所有文章提供原理分析、代码及波形&#xff0c;所有代码均经过本人验证。目录如下&#xff1a;1.数字IC手撕代码-分频器&#xff08;任意偶数分频&#xff09;2.数字IC手撕代…

九龙证券|阿里+鸿蒙+人工智能+元宇宙概念热度爆棚,“会说话的猫”亮了!

近一周组织调研个股数量有240多只&#xff0c;汤姆猫成为调研组织数量最多的股票。 证券时报数据宝统计&#xff0c;近一周组织调研公司数量有240多家。从调研组织类型来看&#xff0c;证券公司调研相对最广泛&#xff0c;调研230多家公司。 “会说话的猫”亮了 汤姆猫成为近…

倒计时3天:现实与虚拟交织,元宇宙警察将如何执法?

在元宇宙、Web3高速发展的时代&#xff0c;欧科云链以科技助警&#xff0c;帮助公安等机构实现对新型犯罪的监管与侦破。 ——摘要元宇宙作为应用场景和生活方式的未来&#xff0c;拥有着巨大的发展潜力。伴随5G网络、云计算、区块链等技术迅速发展&#xff0c;虚拟现实、人机交…

java面试题-JVM类加载机制

类加载的生命周期&#xff1f;1. 加载阶段&#xff08;Loading&#xff09;在Java程序中&#xff0c;当需要使用某个类时&#xff0c;JVM会使用类加载器来查找并加载该类文件。类加载器会首先从文件系统或网络中查找相应的 .class 文件&#xff0c;读取类的二进制数据&#xff…

【JDK8新特性之方法引用-案例实操】

一.JDK8新特性之方法引用-案例实操 之前我们学习了Stream流以及Lambda表达式相关的内容&#xff0c;如果想看的同学可以看一下之前的文章&#xff0c;接下来我们就来学习让Lambda表达式更加简洁的方法引用。 二. 什么是方法引用&#xff1f;为什么要使用方法引用&#xff1f; …