linux 系统调用

2023-11-04

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! 

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

 

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

linux 系统调用 的相关文章

  • 如何获取与 shell 中的文件名模式匹配的所有文件的总文件大小?

    我正在尝试仅使用 shell 来计算与文件名模式匹配的所有文件 在目录树中 的总大小 以字节为单位 这是我到目前为止所拥有的 find name undo exec stat c s awk 总计 1 END 打印总计 有没有更简单的方法来
  • Vagrant 遇到问题 - “404 - 未找到”

    我正在尝试使用 Vagrant 制作一个 LAMP 盒子 有人告诉我它使用起来非常简单 我对网络和虚拟机完全陌生 对 Linux Ubuntu 的经验也很少 我目前已尝试按照官方文档页面上的教程进行操作 http docs vagrantu
  • 如何通过 DOS 批处理命令发送电子邮件?

    我在 DOS 中有一个批处理文件 可以进行一些检查 完成后我需要发送一封电子邮件 我在 interwebz 上找到了一些解决方案 但大多数都是第三方的 或者只是在 Outlook 中打开新邮件 我需要命令来发送完整的电子邮件 而无需任何人工
  • 链接错误:命令行中缺少 DSO

    我对 Linux 使用 Ubuntu 14 04 LTS 64 位 相当陌生 来自 Windows 并且正在尝试移植我现有的 CUDA 项目 当通过链接时 usr local cuda bin nvcc arch compute 30 co
  • 为什么此 NASM 代码会打印我的环境变量?

    本学期我刚刚完成计算机体系结构课程 除其他外 我们一直在涉足 MIPS 汇编并在 MARS 模拟器中运行它 今天 出于好奇 我开始在我的 Ubuntu 机器上摆弄 NASM 基本上只是将教程中的内容拼凑起来 并感受一下 NASM 与 MIP
  • Linux shell 从用户输入中获取设备 ID

    我正在为一个程序编写安装脚本 该程序需要在其配置中使用 lsusb 的设备 ID 因此我正在考虑执行以下操作 usblist lsusb put the list into a array for each line use the arr
  • 如何使用 git hook pre-merge-commit 获取原始合并分支名称

    我正在尝试使用新的 git hook pre merge commit 创建一个特定的脚本 但它没有参数 有什么解决方法可以让我获得正在合并的分支的名称吗 例子 在分支 myBranch 上 我调用 git merge testingBra
  • 使用 libusb 输出不正确

    我用libusb编写了一个程序 我怀疑输出是否正确 因为所有条目都显示相同的供应商和产品 ID 以下是代码 include
  • unix 下日期字段排序

    我有包含数十万条记录的文本文件 其中一个字段是日期字段 有没有办法根据日期字段对文件进行排序 09 APR 12 04 08 43 632279000 AM 19 MAR 12 03 53 38 189606000 PM 19 MAR 12
  • 正则表达式删除块注释也删除 * 选择器

    我正在尝试使用 bash 从 css 文件中删除所有块注释 我有以下 sed 命令的正则表达式 sed r s w s w d 这可以很好地去除块注释 例如 This is a comment this is another comment
  • Linux无法删除文件

    当我找到文件时 我在删除它们时遇到问题 任务 必须找到带有空格的文件并将其删除 我的尝试 rm find L root grep i 但我有错误 rm cannot remove root test No such file or dire
  • 在汇编中使用 printf 会导致管道传输时输出为空,但可以在终端上使用

    无输出 https stackoverflow com questions 54507957 printf call from assembly do not print to stdout即使在终端上 当输出不包含换行符时也有相同的原因
  • Linux 使用 boost asio 拒绝套接字绑定权限

    我在绑定套接字时遇到问题 并且以用户身份运行程序时权限被拒绝 这行代码会产生错误 acceptor new boost asio ip tcp acceptor io boost asio ip tcp endpoint boost asi
  • 如何在 Linux 上通过 FTP 递归下载文件夹 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案
  • 在压缩存档内的文本文件上运行“head”,而不解压存档

    问候 我接手了之前的团队并编写了处理 csv 文件的 ETL 作业 我在 ubuntu 上结合使用 shell 脚本和 perl csv 文件很大 它们以压缩档案形式到达 解压后 很多都超过 30Gb 是的 那是 G 旧进程是在 cron
  • 如何在不使用 IDE 的情况下在 Linux 上运行 Java 项目

    我是 Java 新手 基本上 我开发了一个java项目 其中包含Eclipse中的多个Java包 该项目在我安装了 redhat Linux 的桌面上运行正常 然而 我需要在一个更强大的没有安装X11的Linux服务器 redhat ent
  • 在 docker 中重定向命令输出

    我想为我的服务器做一些简单的日志记录 它是一个在 Docker 容器中运行的小型 Flask 应用程序 这是 Dockerfile Dockerfile FROM dreen flask MAINTAINER dreen WORKDIR s
  • 执行命令而不将其保留在历史记录中[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 在进行软件开发时 经常需要在命令行命令中包含机密信息 典型示例是将项目部署到服务器的凭据设置为环境变量 当我不想将某些命令存储在命令历史记
  • Python 中的二进制缓冲区

    在Python中你可以使用StringIO https docs python org library struct html用于字符数据的类似文件的缓冲区 内存映射文件 https docs python org library mmap
  • 如何制作 Bash 脚本来查找项目中未使用的图像?

    如何制作一个 Bash shell 脚本 它可以识别所有 jpg gif 和 png 文件 然后识别文件夹中任何文本文件中哪些文件未通过 url href 或 src 链接 这就是我开始的 但我最终得到了与我想要的相反的结果 我不想知道引用

随机推荐

  • 软件测试中静态测试和动态测试的区别

    1 测试的部分不同 静态测试是指测试不运行的部分 只是检查和审阅 如规范测试 软件模型测试 文档测试等 动态测试是通常意义上的测试 也就是运行和使用软件 2 测试方式不同 静态测试 通过评审文档 阅读代码等方式测试软件称为静态测试 通过运行
  • 【Python入门系列】第十四篇:Python Web开发

    文章目录 前言 一 PythonWeb开发简介 二 开发准备工作 三 开发步骤 四 开发案例 1 使用Flask框架创建一个简单的Web应用程序 2 使用Django框架创建一个简单的待办事项应用程序 3 使用Flask框架创建一个简单的博
  • 【数据挖掘】(一)用jupyter编程

    为熟悉jupyter 找了一本书练习 参考资料 Python数据挖掘入门与实践 数据集 https github com packtpublishing learning data mining with python 第一行代码 impo
  • 测试用例设计方法

    一 等价类划分 二 边界值分析法 三 场景法 四 判定表 五 因果图 六 错误推测法 七 正交试验法 一 等价类划分 定义 依据需求将输入划分成若干个等价类 从等价类中选定一个测试用例 如果该用例通过 则表明整个等价类通过 适用范围 适用于
  • MYSQL常见查询-SELECT

    1 查看某一产品销量城市排行榜前10名 并展示其同环比 思路 取出今日 昨日 上周该商品的销量 通过开窗函数计算同环比 代码 SELECT FROM SELECT n FIRST VALUE n num OVER PARTITION by
  • Ubuntu:vim键盘上下左右按键变ABCD

    原因 ubuntu自带vi不完整导致 解决方法一 sudo apt get remove vim common sudo apt get install vim 解决方法二 sudo apt get install vim gtk
  • 在Mac上配置Vue开发环境

    因为Vue是NodeJS的模块 要想使用Vue需要先安装NodeJS 在Mac中安装NodeJS通过brew包管理器就会很方便 因为访问源速度问题建议使用5 阿里 Homebrew开源项目地址 https gitee com cunkai
  • 抱抱脸(hugging face)教程-中文翻译-分享一个模型

    分享一个模型 最后两个教程展示了如何使用 PyTorch Keras 和 Accelerate 优化分布式设置的模型 下一步就是把你的模型公之于众 我们相信公开分享知识和资源 使人工智能大众化 我们鼓励你考虑与社区分享你的模式 以帮助其他人
  • 史上最全!Selenium 录制脚本+八种元素定位方式+具体代码演示

    话不多说 先附上练习的所有代码链接 link 文章目录 引言 什么是自动化测试 一 selenium定义 二 使用selenium IDE录制脚本 三 元素的定位方式 1 id gt find element by id 2 name gt
  • 欧拉筛法代码及数学原理

    数学原理 首先 从 2 开始 把 2 的倍数都标记为合数 然后把下一个未标记的数 3 标记为素数 再把 3 的倍数标记为合数 接着把下一个未标记的数 5 标记为素数 再把 5 的倍数标记为合数 以此类推 直到标记到 N 为止 在标记的过程中
  • Java #{} 和 ${} 的含义及区别

    表示一个占位符 向占位符输入参数 MyBatis 会自动进行 Java 类型和 jdbc 类型的转换 且不需要考虑参数的类型 以预编译的方式传入 可以有效的防止 SQL 注入 提高系统安全性 例如 传入字符串 MyBatis 最终拼接好的
  • 学会了c语言怎么编程,三天学会C语言编程

    本文试图通过上中下三篇文章引领大家进入C语言的世界 C语言是一个非常古老 1972年发明 的语言了 想必大家都有所了解 没有了解也没关系 C语言以难学和难以使用着称 想用好C语言更是难上加难 本文不假设读者有任何其它编程语言的基础 但需要了
  • 安装或卸载Anaconda后Windows自带的cmd命令行窗口会闪退

    问题现象 Anaconda初次安装或者重装后 如果打开Windows系统自带的cmd命令窗口 会马上闪退 Win R 输入cmd就闪退 Win R 输入cmd d可以正常打开 解决方案 网上很多给出的解决方案是 按Win R 输入reged
  • Invalid configuration `x86_64-unknown-linux-gnu': machine

    checking host system type Invalid configuration x86 64 unknown linux gnu machine x86 64 unknown not recognized 在做 config
  • Gamemaker studio2经验(4)——打字机效果

    问题概述 在很多游戏中 算了实在不好意思写引言了 就直说啦 如果你是UT粉 想用gm搞搞UT的同人作品但是又无从下手 那么请看过来 对于RPG类游戏 文字交流系统是不可或缺的 但是gm的文字系统 实在有些一言难尽 那没办法 谁让gm是真爱呢
  • 本地计算机架设http服务器,Http File Server(简易Http服务器服务端)

    如果您感觉配置IIS和apache等web服务端太麻烦的话 不妨试试Http File Server Http File Server是一套简易的Http服务器服务端系统 它无需安装 运行后简单配置一下就可以开放80端口了 与传统Web服务
  • iframe嵌入本地视频或者http链接视频禁止自动播放

    本地视频禁止自动播放 http链接视频禁止自动播放 在视频链接后加上 autoplay 0即可
  • windows连网疑问

    如果我的笔记本连接一台无线AP 当我不更改SSID 而只去更改密码的时候 为什么会连不上 当我把电脑里关于这个AP的连接信息删掉之后 就能够连接上了 所以 是跟之前保存的信息有关系的 但其中的作用原理不是很清楚 不过 我想 这必定是wind
  • ubuntu16.04下teamviewer启动不显示界面

    导读 在Ubuntu下使用teamviewer的时候 通过命令行输入 teamviewer 不会出现界面 就像这样 没有显示teamviewer的界面 adminuser adminuser pc teamviewer Init Check
  • linux 系统调用

    5 1 5 如何使用系统调用 如图5 2所示 用户应用可以通过两种方式使用系统调用 第一种方式是通过C库函数 包括系统调用在C库中的封装函数和其他普通函数 图5 2 使用系统调用的两种方式 第二种方式是使用 syscall宏 2 6 18版