linux 系统调用

5.1.5  如何使用系统调用

如图5.2所示,用户应用可以通过两种方式使用系统调用。第一种方式是通过C库函数,包括系统调用在C库中的封装函数和其他普通函数。

 
图5.2  使用系统调用的两种方式

第二种方式是使用_syscall宏。2.6.18版本之前的内核,在include/asm-i386/unistd.h文件中定义有7个_syscall宏,分别是:

  1. _syscall0(type,name)  
  2. _syscall1(type,name,type1,arg1)  
  3. _syscall2(type,name,type1,arg1,type2,arg2)  
  4. _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)  
  5. _syscall4(type,name,type1,arg1,type2,arg2,type3, arg3,type4,arg4)  
  6. _syscall5(type,name,type1,arg1,type2,arg2,type3, arg3,type4,arg4,type5,arg5)  
  7. _syscall6(type,name,type1,arg1,type2,arg2,type3, arg3,type4,arg4,type5,arg5,type6,arg6) 

其中,type表示所生成系统调用的返回值类型,name表示该系统调用的名称,typeN、argN分别表示第N个参数的类型和名称,它们的数目和_syscall后面的数字一样大。这些宏的作用是创建名为name的函数,_syscall后面跟的数字指明了该函数的参数的个数。

比如sysinfo系统调用用于获取系统总体统计信息,使用_syscall宏定义为:

  1. _syscall1(int, sysinfo, struct sysinfo *, info); 

展开后的形式为:

  1. int sysinfo(struct sysinfo * info)  
  2. {  
  3.     long __res;  
  4.     __asm__ volatile("int $0x80" : "=a" (__res) : "0" (116),"b" ((long)(info)));  
  5.     do {  
  6.         if ((unsigned long)(__res) >= (unsigned long)(-(128 + 1))) {  
  7.             errno = -(__res);  
  8.             __res  = -1;  
  9.         }  
  10.         return (int) (__res);  
  11.     } while (0);  

可以看出,_syscall1(int, sysinfo, struct sysinfo *, info)展开成一个名为sysinfo的函数,原参数int就是函数的返回类型,原参数struct sysinfo *和info分别构成新函数的参数。

在程序文件里使用_syscall宏定义需要的系统调用,就可以在接下来的代码中通过系统调用名称直接调用该系统调用。下面是一个使用sysinfo系统调用的实例。

代码清单5.1  sysinfo系统调用使用实例

  1. 00 #include <stdio.h> 
  2. 01 #include <stdlib.h> 
  3. 02 #include <errno.h> 
  4. 03 #include <linux/unistd.h>         
  5. 04 #include <linux/kernel.h>       /* for struct sysinfo */  
  6. 05   
  7. 06 _syscall1(int, sysinfo, struct sysinfo *, info);  
  8.      07   
  9. 08 int main(void)  
  10. 09 {  
  11. 10     struct sysinfo s_info;  
  12. 11     int error;  
  13. 12   
  14. 13     error = sysinfo(&s_info);  
  15. 14     printf("code error = %d/n", error);  
  16. 15     printf("Uptime = %lds/nLoad: 1 min %lu / 5 min %lu / 15 min %lu/n"  
  17. 16         "RAM: total %lu / free %lu / shared %lu/n"  
  18. 17         "Memory in buffers = %lu/nSwap: total %lu / free %lu/n"  
  19. 18         "Number of processes = %d/n",  
  20. 19         s_info.uptime, s_info.loads[0],  
  21. 20         s_info.loads[1], s_info.loads[2],  
  22. 21         s_info.totalram, s_info.freeram,  
  23. 22         s_info.sharedram, s_info.bufferram,  
  24. 23         s_info.totalswap, s_info.freeswap,  
  25. 24         s_info.procs);  
  26. 25         exit(EXIT_SUCCESS);  
  27.      26 } 

但是自2.6.19版本开始,_syscall宏被废除,我们需要使用syscall函数,通过指定系统调用号和一组参数来调用系统调用。

syscall函数原型为:

  1. int syscall(int number, ...); 

其中number是系统调用号,number后面应顺序接上该系统调用的所有参数。下面是gettid系统调用的调用实例。

代码清单5.2  gettid系统调用使用实例

  1. 00 #include <unistd.h> 
  2. 01 #include <sys/syscall.h> 
  3. 02 #include <sys/types.h> 
  4. 03   
  5. 04 #define __NR_gettid      224  
  6. 05   
  7. 06 int main(int argc, char *argv[])  
  8. 07 {  
  9. 08     pid_t tid;  
  10. 09   
  11. 10     tid = syscall(__NR_gettid);  
  12. 11 } 

大部分系统调用都包括了一个SYS_符号常量来指定自己到系统调用号的映射,因此上面第10行可重写为:

  1. tid = syscall(SYS_gettid); 
 

5.2 系统调用执行过程

系统调用的执行过程主要包括如图5.3与图5.4所示的两个阶段:用户空间到内核空间的转换阶段,以及系统调用处理程序system_call函数到系统调用服务例程的阶段。

 
图5.3  用户空间到内核空间
 
图5.4  system_call函数到系统调用服务例程

(1)用户空间到内核空间。

如图5.3所示,系统调用的执行需要一个用户空间到内核空间的状态转换,不同的平台具有不同的指令可以完成这种转换,这种指令也被称作操作系统陷入(operating system trap)指令。

Linux通过软中断来实现这种陷入,具体对于X86架构来说,是软中断0x80,也即int $0x80汇编指令。软中断和我们常说的中断(硬件中断)不同之处在于-它由软件指令触发而并非由硬件外设引发。

int 0x80指令被封装在C库中,对于用户应用来说,基于可移植性的考虑,不应该直接调用int $0x80指令。陷入指令的平台依赖性,也正是系统调用需要在C库进行封装的原因之一。

通过软中断0x80,系统会跳转到一个预设的内核空间地址,它指向了系统调用处理程序(不要和系统调用服务例程相混淆),即在arch/i386/kernel/entry.S文件中使用汇编语言编写的system_call函数。

(2)system_call函数到系统调用服务例程。

很显然,所有的系统调用都会统一跳转到这个地址进而执行system_call函数,但正如前面所述,到2.6.23版为止,内核提供的系统调用已经达到了325个,那么system_call函数又该如何派发它们到各自的服务例程呢?

软中断指令int 0x80执行时,系统调用号会被放入eax寄存器,同时,sys_call_table每一项占用4个字节。这样,如图5.5所示,system_call函数可以读取eax寄存器获得当前系统调用的系统调用号,将其乘以4生成偏移地址,然后以sys_call_table为基址,基址加上偏移地址所指向的内容即是应该执行的系统调用服务例程的地址。

另外,除了传递系统调用号到eax寄存器,如果需要,还会传递一些参数到内核,比如write系统调用的服务例程原型为:

  1. sys_write(unsigned int fd, const char * buf, size_t count); 

调用write系统调用时就需要传递文件描述符fd、要写入的内容buf以及写入字节数count等几个内容到内核。ebx、ecx、edx、esi以及edi寄存器可以用于传递这些额外的参数。

正如之前所述,系统调用服务例程定义中的asmlinkage标记表示,编译器仅从堆栈中获取该函数的参数,而不需要从寄存器中获得任何参数。进入system_call函数前,用户应用将参数存放到对应寄存器中,system_call函数执行时会首先将这些寄存器压入堆栈。

对于系统调用服务例程,可以直接从system_call函数压入的堆栈中获得参数,对参数的修改也可以一直在堆栈中进行。在system_call函数退出后,用户应用可以直接从寄存器中获得被修改过的参数。

并不是所有的系统调用服务例程都有实际的内容,有一个服务例程sys_ni_syscall除了返回-ENOSYS外不做任何其他工作,在kernel/sys_ni.c文件中定义。

  1. 10 asmlinkage long sys_ni_syscall(void)  
  2. 11 {  
  3. 12      return -ENOSYS;  
  4. 13 } 

sys_ni_syscall的确是最简单的系统调用服务例程,表面上看,它可能并没有什么用处,但是,它在sys_call_table中占据了很多位置。多数位置上的sys_ni_syscal都代表了那些已经被内核中淘汰的系统调用,比如:

  1. .long sys_ni_syscall    /* old stty syscall holder */  
  2. .long sys_ni_syscall    /* old gtty syscall holder */ 

就分别代替了已经废弃的stty和gtty系统调用。如果一个系统调用被淘汰,它所对应的服务例程就要被指定为sys_ni_syscall。

我们并不能将它们的位置分配给其他的系统调用,因为一些老的代码可能还会使用到它们。否则,如果某个用户应用试图调用这些已经被淘汰的系统调用,所得到的结果,比如打开了一个文件,就会与预期完全不同,这将令人感到非常奇怪。

其实,sys_ni_syscall中的"ni"即表示"not implemented(没有实现)"。

系统调用通过软中断0x80陷入内核,跳转到系统调用处理程序system_call函数,并执行相应的服务例程,但由于是代表用户进程,所以这个执行过程并不属于中断上下文,而是处于进程上下文。

因此,系统调用执行过程中,可以访问用户进程的许多信息,可以被其他进程抢占(因为新的进程可能使用相同的系统调用,所以必须保证系统调用可重入),可以休眠(比如在系统调用阻塞时或显式调用schedule函数时)。

这些特点涉及进程调度的问题,在此不做深究,读者只需要理解当系统调用完成后,把控制权交回到发起调用的用户进程前,内核会有一次调度。如果发现有优先级更高的进程或当前进程的时间片用完,那么就会选择高优先级的进程或重新选择进程运行。

 

 

5.3 系统调用示例

本节通过对几个系统调用的剖析来讲解它们的工作方式。

5.3.1  sys_dup

dup系统调用的服务例程为sys_dup函数,在fs/fcntl.c文件中定义如下。

代码清单5.3  dup系统调用的服务例程

  1. 192 asmlinkage long sys_dup(unsigned int fildes)  
  2. 193 {  
  3. 194     int ret = -EBADF;  
  4. 195     struct file * file = fget(fildes);  
  5. 196  
  6. 197     if (file)  
  7. 198         ret = dupfd(file, 0);  
  8. 199     return ret;  
  9. 200 } 

除了sys_ni_call()以外,sys_dup()称得上是最简单的服务例程之一,但是它却是Linux输入/输出重定向的基础。

在Linux中,执行一个shell命令时通常会自动打开3个标准文件:标准输入文件(stdin),通常对应终端的键盘;标准输出文件(stdout)和 标准错误输出文件(stderr),通常对应终端的屏幕。shell命令从标准输入文件中得到输入数据,将输出数据输出到标准输出文件,而将错误信息输出到标准错误文件中。

比如下面的命令:

  1. $cat /proc/cpuinfo 

将把cpuinfo文件的内容显示到屏幕上,但是如果cat命令不带参数,则会从stdin中读取数据,并将其输出到stdout,比如:

  1. $cat  
  2. Hello!  
  3. Hello! 

用户输入的每一行都将立刻被输出到屏幕上。

输入重定向是指把命令的标准输入重定向到指定的文件中,即输入可以不来自键盘,而来自一个指定的文件。所以说,输入重定向主要用于改变一个命令的输入源。

输出重定向是指把命令的标准输出或标准错误输出重新定向到指定文件中。这样,该命令的输出就不显示在屏幕上,而是写入到指定文件中。我们经常会利用输出重定向将程序或命令的log保存到指定的文件中。

那么sys_dup()又是如何完成输入/输出的重定向呢?下面通过一个例子进行说明。

当我们在shell终端下输入"echo hello"命令时,将会要求shell进程执行一个可执行文件echo,参数为"hello"。当shell进程接收到命令之后,先在/bin目录下找到echo文件(我们可以使用which命令获得命令所在的位置),然后创建一个子进程去执行/bin/echo,并将参数传递给它,而这个子进程从shell进程继承了3个标准输入/输出文件,即stdin、stdout和stderr,文件号分别为0、1、2。它的工作很简单,就是将参数"hello"写到stdout文件中,通常都是我们的屏幕上。

但是如果我们将命令改成"echo hello > txt",则在执行时输出将会被重定向到磁盘文件txt中。假定之前该shell进程只有上述3个标准文件打开,则该命令将按如下序列执行。

(1)打开或创建文件txt,如果txt中原来有内容,则清除原来的内容,其文件号为3。

(2)通过dup系统调用复制文件stdout的相关数据结构到文件号4。

(3)关闭stdout,但是由于4号文件也同时引用stdout,所以stdout文件并未真正关闭,只是腾出1号文件号位置。

(4)通过dup系统调用,复制3号文件(即文件txt),由于1号文件关闭,其位置空缺,故3号文件被复制到1号,即进程中原来指向stdout的指针指向了txt。

(5)通过系统调用fork和exec创建子进程并执行echo,子进程在执行cat前关闭3号和4号文件,只留下0、1、2三个文件,请注意,这时的1号文件已经不是stdout而是文件txt了。当cat想向stdout文件写入"hello"时自然就写入到了txt中。

(6)回到shell进程后,关闭指向txt的1号与3号文件文件,再用dup和close系统调用将2号恢复至stdout,这样shell就恢复了0、1、2三个标准输入/输出文件。

 

5.3.2  sys_reboot

Linux下有关关机与重启的命令主要有shutdown、reboot、halt、poweroff、telinit和init。它们都可以达到关机或重启的目的,但是每个命令的工作流程并不一样。

这些命令并不都是互相独立的,比如,poweroff、reboot即是halt的符号链接,但是它们最终都是通过reboot系统调用来完成关机或重启操作。

reboot系统调用的服务例程为sys_reboot函数,在kernel/sys.c文件中定义如下。

代码清单5.4  reboot系统调用的服务例程

  1. 896 asmlinkage long sys_reboot(int magic1,  int magic2, unsigned int cmd, void __user * arg)  
  2. 897 {  
  3. 898     char buffer[256];  
  4. 899   
  5. 900     /* We only trust the superuser with rebooting the system. */  
  6. 901     if (!capable(CAP_SYS_BOOT))  
  7. 902         return -EPERM;  
  8. 903   
  9. 904     /* For safety, we require "magic" arguments. */  
  10. 905     if (magic1 != LINUX_REBOOT_MAGIC1 ||  
  11. 906         (magic2 != LINUX_REBOOT_MAGIC2 &&  
  12. 907                     magic2 != LINUX_REBOOT_MAGIC2A &&  
  13. 908             magic2 != LINUX_REBOOT_MAGIC2B &&  
  14. 909                     magic2 != LINUX_REBOOT_MAGIC2C))  
  15. 910         return -EINVAL;  
  16. 911   
  17. 912     /* Instead of trying to make the power_off code look like  
  18. 913      * halt when pm_power_off is not set do it the easy way.  
  19. 914      */  
  20. 915     if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)  
  21. 916         cmd = LINUX_REBOOT_CMD_HALT;  
  22. 917   
  23. 918     lock_kernel();  
  24. 919     switch (cmd) {  
  25. 920     case LINUX_REBOOT_CMD_RESTART:  
  26. 921         kernel_restart(NULL);  
  27. 922         break;  
  28. 923   
  29. 924     case LINUX_REBOOT_CMD_CAD_ON:  
  30. 925         C_A_D = 1;  
  31. 926         break;  
  32. 927   
  33. 928     case LINUX_REBOOT_CMD_CAD_OFF:  
  34. 929         C_A_D = 0;  
  35. 930         break;  
  36. 931   
  37. 932     case LINUX_REBOOT_CMD_HALT:  
  38. 933         kernel_halt();  
  39. 934         unlock_kernel();  
  40. 935         do_exit(0);  
  41. 936         break;  
  42. 937   
  43. 938     case LINUX_REBOOT_CMD_POWER_OFF:  
  44. 939         kernel_power_off();  
  45. 940         unlock_kernel();  
  46. 941         do_exit(0);  
  47. 942         break;  
  48. 943   
  49. 944     case LINUX_REBOOT_CMD_RESTART2:  
  50. 945         if (strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) {  
  51. 946             unlock_kernel();  
  52. 947             return -EFAULT;  
  53. 948         }  
  54. 949         buffer[sizeof(buffer) - 1] = '/0';  
  55. 950   
  56. 951         kernel_restart(buffer);  
  57. 952         break;  
  58. 953   
  59. 954     case LINUX_REBOOT_CMD_KEXEC:  
  60. 955         kernel_kexec();  
  61. 956         unlock_kernel();  
  62. 957         return -EINVAL;  
  63. 958   
  64. 959 #ifdef CONFIG_HIBERNATION  
  65. 960     case LINUX_REBOOT_CMD_SW_SUSPEND:  
  66. 961         {  
  67. 962             int ret = hibernate();  
  68. 963             unlock_kernel();  
  69. 964             return ret;  
  70. 965         }  
  71. 966 #endif  
  72. 967   
  73. 968     default:  
  74. 969         unlock_kernel();  
  75. 970         return -EINVAL;  
  76. 971     }  
  77. 972     unlock_kernel();  
  78. 973     return 0;  
  79. 974 } 

顾名思义,reboot系统调用可以用于重新启动系统,但根据所提供的参数不同,它还能够完成关机、挂起系统、允许或禁止使用Ctrl+Alt+Del组合键重启等不同的操作。我们还要特别注意内核里对sys_reboot()的注释,在使用它之前首先要使用sync命令同步磁盘,否则磁盘上的文件系统可能会有所损坏。

第901行检查调用者是否有合法权限。capable函数用于检查是否有操作指定资源的权限,如果它返回非零值,则调用者有权进行操作,否则无权操作。比如,这一行的capable(CAP_SYS_BOOT)即检查调用者是否有权限使用reboot系统调用。

第905行~第910行通过对两个参数magic1和magic2的检测,判断reboot系统调用是不是被偶然调用到的。如果reboot系统调用是被偶然调用的,那么参数magic1和magic2几乎不可能同时满足预定义的这几个数字的集合。

从第919行开始,sys_reboot()对调用者的各种使用情况进行区分。为LINUX_REBOOT_CMD_RESTART时,kernel_restart()将打印出"Restarting system."消息,然后调用machine_restart函数重新启动系统。

为LINUX_REBOOT_CMD_CAD_ON或LINUX_REBOOT_CMD_CAD_OFF时,分别允许或禁止Ctrl+Alt+Del组合键。我们还可以在/etc/inittab文件指定是否可以使用Ctrl+Alt+Del组合键来关闭并重启系统。如果希望完全禁止这个功能,需要将/etc/inittab文件中的下面一行注释掉。

  1. ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now 

为LINUX_REBOOT_CMD_HALT时,打印出"System halted."消息,和LINUX_REBOOT_CMD_RESTART情况下类似,但只是暂停系统而不是将其重新启动。

为LINUX_REBOOT_CMD_POWER_OFF时,打印出"Power down."消息,然后关闭机器电源。

为LINUX_REBOOT_CMD_RESTART2时,接收命令字符串,该字符串说明了系统应该如何关闭。

最后,LINUX_REBOOT_CMD_SW_SUSPEND用于使系统休眠。

5.4 系统调用的实现

一个系统调用的实现并不需要去关心如何从用户空间转换到内核空间,以及系统调用处理程序如何去执行,你需要做的只是遵循几个固定的步骤。

5.4.1  如何实现一个新的系统调用

为Linux添加新的系统调用是件相对容易的事情,主要包括有4个步骤:编写系统调用服务例程;添加系统调用号;修改系统调用表;重新编译内核并测试新添加的系统调用。

下面以一个并无实际用处的hello系统调用为例,来演示上述几个步骤。

(1)编写系统调用服务例程。

遵循前面所述的几个原则,hello系统调用的服务例程实现为:

  1. 01 asmlinkage long sys_hello(void)  
  2. 02 {  
  3. 03      printk("Hello!/n");  
  4. 04      return 0;  
  5. 05 } 

通常,应该为新的系统调用服务例程创建一个新的文件进行存放,但也可以将其定义在其他文件之中并加上注释做必要说明。同时,还要在include/linux/syscalls.h文件中添加原型声明:

  1. asmlinkage long sys_hello(void); 

sys_hello函数非常简单,仅仅打印一条语句,并没有使用任何参数。如果我们希望hello系统调用不仅能打印"hello!"欢迎信息,还能够打印出我们传递过去的名称,或者其他的一些描述信息,则sys_hello函数可以实现为:

  1. 01 asmlinkage long sys_hello(const char __user *_name)  
  2. 02 {  
  3. 03      char *name;  
  4. 04      long ret;  
  5. 05  
  6. 06      name = strndup_user(_name, PAGE_SIZE);  
  7. 07      if (IS_ERR(name)) {  
  8. 08          ret = PTR_ERR(name);  
  9. 09          goto error;  
  10. 10      }  
  11. 11  
  12. 12      printk("Hello, %s!/n", name);  
  13. 13      return 0;  
  14. 14  error:  
  15. 15      return ret;  
  16. 16 } 

第二个sys_hello函数使用了一个参数,在这种有参数传递发生的情况下,编写系统调用服务例程时必须仔细检查所有的参数是否合法有效。因为系统调用在内核空间执行,如果不加限制任由用户应用传递输入进入内核,则系统的安全与稳定将受到影响。

参数检查中最重要的一项就是检查用户应用提供的用户空间指针是否有效。比如上述sys_hello函数参数为char类型指针,并且使用了__user标记进行修饰。__user标记表示所修饰的指针为用户空间指针,不能在内核空间直接引用,原因主要如下。

用户空间指针在内核空间可能是无效的。

用户空间的内存是分页的,可能引起页错误。

如果直接引用能够成功,就相当于用户空间可以直接访问内核空间,产生安全问题。

因此,为了能够完成必须的检查,以及在用户空间和内核空间之间安全地传送数据,就需要使用内核提供的函数。比如在sys_hello函数的第6行,就使用了内核提供的strndup_user函数(在mm/util.c文件中定义)从用户空间复制字符串name的内容。

(2)添加系统调用号。

每个系统调用都会拥有一个独一无二的系统调用号,所以接下来需要更新include/asm-i386/unistd.h文件,为hello系统调用添加一个系统调用号。

  1. 328 #define __NR_utimensat      320  
  2. 329 #define __NR_signalfd       321  
  3. 330 #define __NR_timerfd        322  
  4. 331 #define __NR_eventfd        323  
  5. 332 #define __NR_fallocate      324  
  6. 333 #define __NR_hello          325 /*分配hello系统调用号为325*/  
  7. 334  
  8. 335 #ifdef __KERNEL__  
  9. 336  
  10. 337 #define NR_syscalls 326 /*将系统调用数目加1修改为326*/ 

(3)修改系统调用表。

为了让系统调用处理程序system_call函数能够找到hello系统调用,我们还需要修改系统调用表sys_call_table,放入服务例程sys_hello函数的地址。

  1. 322 .long sys_utimensat     /* 320 */  
  2. 323 .long sys_signalfd  
  3. 324 .long sys_timerfd  
  4. 325 .long sys_eventfd  
  5. 326 .long sys_fallocate  
  6. 327 .long sys_hello /*hello系统调用服务例程*/ 

新的系统调用hello的服务例程被添加到了sys_call_table的末尾。我们可以注意到,sys_call_table每隔5个表项就会有一个注释,表明该项的系统调用号,这个好习惯可以在查找系统调用对应的系统调用号时提供方便。

(4)重新编译内核并测试。

为了能够使用新添加的系统调用,需要重新编译内核,并使用新内核重新引导系统。然后,我们还需要编写测试程序对新的系统调用进行测试。针对hello系统调用的测试程序如下:

  1. 00 #include <unistd.h> 
  2. 01 #include <sys/syscall.h> 
  3. 02 #include <sys/types.h> 
  4. 03  
  5. 04 #define __NR_hello       325  
  6. 05  
  7. 06 int main(int argc, char *argv[])  
  8. 07 {  
  9. 08      syscall(__NR_hello);  
  10. 09      return 0;  
  11. 10 } 

然后使用gcc编译并执行:

  1. $gcc -o hello hello.c  
  2. $./hello  
  3. Hello! 

由执行结果可见,系统调用添加成功。

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

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

相关文章

python3 csv读写_python3 csv

一、python3 csv 的中文乱码解决方案 将文件保存为 csv 格式的话&#xff0c;用记事本打开是没有问题的&#xff0c;但用excel 打开就会乱码&#xff0c;在网上找了些解决方法都是适用python2 这里提供下一个解决方案 >>> import csv >>> import codecs >…

通用返回_Springboot项目整合通用mapper

1.简介什么是通用mapper什么是通用mapper&#xff0c;用一句话概括就是&#xff0c;它就是一个辅助mybatis开发的组件&#xff0c;它不是替代mybatis&#xff0c;而是使mybatis更方便的开发。通用mapper提供极其方便的单表的增删改查&#xff0c;可以按照自己的需要使用通用方法…

差值平方和匹配_机器学习实战 | 简单目标识别与意图分析之模板匹配

(点击上方快速关注并设置为星标&#xff0c;一起学Python)一天&#xff0c;我正在学校楼下撸猫&#xff0c;同学发来消息&#xff0c;他的老师给了他一个研究课题&#xff0c;大致的方向是对图片或者视频里面的内容进行识别&#xff0c;然后判断意图&#xff0c;而且举了个例子…

如何创建_如何创建自己的微信圈子?圈子创建运营指南

原创&#xff1a;爱捣鼓的猿 袁小猴最近很多小伙伴在问微信圈子是什么&#xff0c;如何才能创建自己的微信圈子&#xff1f;于是小编花时间去研究了下&#xff0c;整理了一些微信圈子的规则&#xff0c;方便大家使用。一、微信圈子是什么&#xff0c;圈子入口&#xff1f;其实曾…

js 获取鼠标在画布的位置_云凤蝶如何打造媲美 sketch 的自由画布

在 Design Tools 中&#xff0c;组件间的对齐与吸附功能是否好用是决定其画布是否可以高效进行产品设计的关键因素。云凤蝶作为一款快速制作高品质中后台应用的 hpaPaaS 平台&#xff0c;同样拥有自由拖拽的可视化画布。那么在云凤蝶的自由画布中&#xff0c;对齐规则是怎样的&…

Linux下TI omap芯片 MUX 配置分析(以AM335X芯片为例)

在移植内核的时候&#xff0c;通常会遇到引脚复用&#xff08;MUX&#xff09;的配置问题。在现在的Linux内核中&#xff0c;对于TI的ARM芯片&#xff0c;早已经有了比较通用的MUX配置框架。这对于许多TI的芯片都是通用的&#xff0c;这次看AM335X的代码顺手写一下分析&#xf…

是网关吗_智能家居网关功能这么多,你都知道吗?

在科技发达的今天&#xff0c;我们的生活也开始趋向于智能化&#xff0c;智能家居已经迎来了新时代。电动窗帘、扫地机器人、电视、空调等电器都能智联wifi&#xff0c;可是使用的时候得一个个去控制&#xff0c;数量多的话懒癌们肯定嫌麻烦的。所以很多聪明的人都选择安装智能…

一个数据包大小是多少k_算法交流: 6046 数据包的调度机制 【2.6基本算法之动态规划】...

【题目描述】 6046 数据包的调度机制 By OIer14wa随着 Internet的迅猛发展,多媒体技术和电子商务应用日益广泛,Internet上的服务质量(QoS,Qualityof Service)问题已越来越受到重视。网络中采用的数据包调度机制与网络的服务质量 QoS 有着密切的关系。研究表明传统的基于队列的调…

iOS vs. Android,应用设计该如何对症下药?

摘要&#xff1a;从iOS到Android&#xff0c;两大平台应用设计有何不同&#xff1f;又都存在什么样的问题&#xff1f;Android定制性太高&#xff0c;该如何进行UI设计&#xff1f;在CMDN CLUB第28期活动中&#xff0c;咕咚网高级产品经理王磊&#xff0c;从iOS、Android谈起&a…

cs8900a网卡驱动--寄存器

1. CS8900内部有一个4k的RAM用于访问其内部寄存器&#xff0c;称为PacketPage。 2. LineCTL 网卡状态设置 从上图看到&#xff0c;此寄存器的6&#xff0c;7位用于设置网卡的收发使能。8&#xff0c;9位用于设置网卡状态。是10BASE-T还是 AUI。下面这图更详细介绍了8&…

jQuery图表插件 JS Charts

JS Charts 是一款免费的基于javascript的轻量级插件&#xff0c;用JS Charts 绘制图表是很轻松地事,因为你只需要关心客户端的脚本。 Loading...Loading...Loading...Loading...Loading...Loading...Loading...Loading...Loading...Loading...Loading...转载于:https://www.cnb…

使用正则把数字前面的符号替换_正则表达式(一) 基本表达式

定义 正则表达式(Regular Expression)用某种模式去匹配一类字符串的公式&#xff0c;主要用来描述字符串匹配的工具。 匹配文本或字符存在不止一个部分满足给定的正则表达式&#xff0c;这是每一个这样的部分都被称为一个匹配。 匹配分为以下三种类型&#xff1a; 形容词性的匹…

jsp思维导图_2019年经济法基础思维导图

参加2019年初级考试的考生们明天可以打印准考证啦时间&#xff1a;2019.4.26-5.5日(传送门&#xff1a;http://kjbm8.mof.gov.cn/ksbm/usercxzkz.jsp)为了帮助大家快速梳理教材考点&#xff0c;下面蓝星职业教育为大家整理了初级会计职称考试各章节思维导图&#xff0c;希望给大…

海量数据持久层解决方案_爱数AnyBackup重磅发布海量非结构化数据超可用解决方案...

海量非结构化数据有三大备份恢复问题一直没有得到有效解决&#xff1a;备份慢、恢复慢、备份数据不可查询。这三大问题已经对行业数字化转型造成了重大阻碍。今天&#xff0c;AnyBackup Family 7线上发布会——重磅发布海量非结构化数据超可用解决方案。AnyBackup以创新超可用技…

wpf 使子ui元素可视区域不超过父元素_对游戏UI设计的一点思考

UI决定了一个游戏的初体验&#xff0c;甚至决定了玩家的初始留存&#xff0c;甚至可以说决定了一个游戏的品质&#xff0c;虽然看起来是表象的&#xff0c;却是直指游戏核心的。简单讲&#xff0c;玩家认可一款游戏永远都是造型场景好&#xff0c;剧情好&#xff0c;画质棒&…

grpc入门到精通_Spring Cloud 从入门到精通(一)Nacos 服务中心初探

点击上方蓝色“Java精选”&#xff0c;选择“设为星标”技术文章第一时间送达&#xff01;什么是Nacos&#xff1f;Nacos是阿里巴巴开源的项目&#xff0c;是一个更易于帮助构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos英文全称是Dynamic Naming and Configur…

百度新年贪吃蛇效果

闲来无事&#xff0c;在网上闲逛的时候开到有人说百度蛇年的贪吃蛇logo小游戏不错&#xff0c;于是乎就自己仿照写了一个。&#xff08;注&#xff1a;所有素材都来自百度&#xff09; 效果图 用到的图片 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional…

贝叶斯公式设b_数据分析经典模型——朴素贝叶斯

编辑导语&#xff1a;做过数据分析的人&#xff0c;想必对贝叶斯模型都不会陌生。贝叶斯预测模型是运用贝叶斯统计进行的一种预测&#xff0c;不同于一般的统计方法&#xff0c;其不仅利用模型信息和数据信息&#xff0c;而且充分利用先验信息。通过实证分析的方法&#xff0c;…

Asterisk使用数据库配置方法

安装&#xff1a; 1、安装 unixODBC unixODBC-devel libtool-ltdl libtool-ltdl-devel &#xff0c;为了使asterisk支持数据库存储&#xff08;必须先安装&#xff09; 2、安装 mysql 并设置好 C_INCLUDE_PATH 和 LD_LIBRARY_PATH 3、从 http://www.asterisk.org/downloads 下载…

linux文件系统_Linux的文件系统简介

inux操作系统的本质可以说就是文件系统的集合&#xff0c;文件系统既包含文件的数据也包含文件系统的结构。在Linux文件系统中&#xff0c;EXT2文件系统、虚拟文件系统、/proc文件系统是三个具有代表性的文件系统。/proc文件系统是一个伪文件系统&#xff0c;它只存在内存当中&…