通过内核源码看函数调用之前世今生

Linux epoll内核源码剖析 IO多路复用接口Linux内核源码剖析,源码之前,了无密码 poll Linux内核源码剖析 select Linux内核源码剖析 epoll Linux内核源码剖析 epoll Linux内核源码剖析 前面介绍了select/poll,此文章将讲解epoll,epoll是select/poll的增强版,epoll更加高效,当然也更加复杂 epoll高效的一个重要的原因是在获取事件的时候,它无须遍... 阅读详情
  • 通过内核源码看函数调用之前世今生
                                                                            作者:杨小华
栈(Stack):一个有序的积累或堆积
                                                               韦氏词典
对每一位孜孜不倦的程序员来说,栈已深深的烙在其脑海中,甚至已经发生变异。栈可以用来传递函数参数、存储局部变量、以及存储返回值的信息、还可以用于保存寄存器的值以供恢复之用。
       在X86平台上(又称之为IA32),应用程序借用栈来支持函数(又称为过程)调用,变量的存储按后进先出(LIFO)的方式进行。
一、      栈帧布局
       在具体讲解函数调用之前,我们先来明确栈的几个概念:满栈与空栈,升序栈与降序栈。
       满栈是指栈指针指向上次写的最后一个数据单元,而空栈的栈指针指向第一个空闲单元。一个降序栈是在内存中反向增长(就是从应用程序空间结束处开始反向增长),而升序栈在内存中正向增长。
       RISC机器使用传统的满降序栈(FD Full Descending)。如果使用符合IA32规定的编译器,它通常把你的栈指针设置在应用程序空间的结束处并接着使用一个满降序栈。用来存放一个函数的局部变量、参数、返回地址和其它临时变量的栈区域称为栈帧(stack frame),如图1所示。
图 1 栈帧结构
       栈帧布局的设计要考虑到指令集的体系结构特征和被编译的程序设计语言的特征。但是,计算机的制造者常常规定一种用于其体系结构的“标准”栈帧布局,以便被所有的程序设计语言编译器采纳。这种栈帧布局对于某些特定的程序设计语言或编译器可能并不是最方便的,但是通过这种“标准”布局,用不同程序设计语言编写的函数得以相互调用。
当P调用Q时,Q的参数是放在P的帧中的。另外,当P调用Q时,P中的下一条指令地址将被压入栈中,形成P的栈帧的末尾,具体可参见图1,返回地址就是当程序从Q返回时应该继续执行的地方。Q的栈帧从保存帧指针的位置开始,后面开始保存其他寄存器的值。Q也会用栈帧来保存其他不能存放在寄存器中的局部变量。如果函数要返回整数或指针的话,常用寄存器%eax来保存返回值。
当程序执行时,栈指针是可以移动的,因此大多数信息的访问都是相对于帧指针(%ebp)的。
二、      寄存器使用惯例
假设函数P(……)调用函数Q(a1,……,an),我们称P是调用者(caller),Q是被调用者(callee)。如果必须被调用者保存和恢复的寄存器,我们称之为调用者保护的寄存器(caller-save);如果是被调用者的责任,则称之为被调用者保护的寄存器(callee-save)。
程序寄存器组是唯一一个被所有函数共享的资源。虽然在给定时刻只能有一个函数是活动的,但是我们必须保证当一个函数调用另一个函数时,被调用者不会覆盖某个调用者稍后会使用的寄存器的值。
为此,任何一个平台都会制订一套标准,让所有的函数都必须遵循,包括程序库中的函数。但在大多数计算机系统结构中,调用者保护的寄存器和被调用者保护的寄存器的概念并不是由硬件来实现的,而是机器参考手册中规定的一种约定。
比如,在ARM体系平台中,所有的函数调用必须遵守ARM 过程调用标准(APCS,ARM Procedure Call Standard)。该标准提供了一套紧凑的代码编写机制,定义的函数可以与其他语言编写的函数交织在一起。其他函数可以编译自 C、 Pascal、也可以是用汇编语言写成的函数。
同理,IA32平台也采用了一套统一的寄存器使用惯例。根据惯例,寄存器%eax、%edx、%ecx被划分为调用者保存。当函数P(调用者)调用Q(被调用者)时,Q可以覆盖这些寄存器的值,而不会破坏任何P所需要的数据。另外,%ebx、%esi、%edi、%ebp被划分为被调用者保存,这意味着Q必须在覆盖他们之前,将这些寄存器的值保存到栈中,并在返回前恢复他们。
三、      参数传递惯例
大约在1960年之前,参数传递不是通过栈来传递的,而是通过一块静态分配的存储空间来传递的,这种方法阻碍了递归函数的使用。从20世纪70年代开始,大多数调用约定函数参数的传递通过栈来实现(因为访问寄存器比访问存储器要快的多),同时也会导致一些不必要的存储器访问。对实际程序的研究表明,很少有函数的参数个数是超过4个,并且极少有6个的。因此,现代计算机中的参数传递约定都规定,一个函数的前k个参数(典型的,k=4或者k=6)放在寄存器中传递,剩余的参数则放在存储器中传递。
在ARM体系平台中,APCS就明确规定:
1)        前 4 个整数实参(或者更少!)被装载到 R0 – R4寄存器中。
2)        前 4 个浮点实参(或者更少!)被装载到 f0 - f3寄存器中。
3)        其他任何实参(如果有的话)存储在内存中,用进入函数时紧接在栈指针所指向的空间。换句话说,其余的参数被压入栈顶。
但在IA32平台上,参数传递不是完全通过寄存器来实现的,而是通过栈帧来实现的。根据不同的调用方式,参数在栈帧的存放方式又有一点差别,区别如下表所示:
调用方式
参数在堆栈里的次序
操作方式
_cdecl 
第一个参数在低位地址
调用者
_stdcall 
第一个参数在低位地址
被调用者
_fastcall 
编译器指定
被调用者
_pascal 
第一个参数在高位地址 
被调用者
Borland 和 GNU 编译器使用 _cdecl 方式,而 Microsoft 使用 _stdcall 方式。通过这里可以发现,当今两大主流的编译器,第一个参数都在低地址,也就是说第一个参数是最后一个压栈的,当被调用函数被调用时,此时就赋值给了第一个形参。
四、      Linux内核源码研究
下面我们以Linux内核中断处理源代码来研究函数调用的本质。本文所提及的内核代码版本为2.6.10,读者需了解GCC汇编语言的基础知识。
当某个中断发生后,将会执行push $i-256,jmp common_interrupt指令,此时将会跳转到common_interrupt处执行。
文件名:arch/i386/kernel/entry.S(说明:前面的数字表示行号)
359                ALIGN
360         common_interrupt:
361                SAVE_ALL
362                movl %esp,%eax
363                call do_IRQ
364                jmp ret_from_intr
 
       这里主要的操作是宏操作SAVE_ALL,就是所谓的“保存现场”,把中断发生前夕所有寄存器的内容都保存在堆栈中,待中断服务完毕返回之前再来“恢复现场”。
文件名:arch/i386/kernel/irq.c
48           fastcall unsigned int do_IRQ(struct pt_regs *regs)
49           {    
50                  /* high bits used in ret_from_ code */
//取得中断向量号
51                  int irq = regs->orig_eax & 0xff;
52           #ifdef CONFIG_4KSTACKS
53                  union irq_ctx *curctx, *irqctx;
54                  u32 *isp;
55           #endif
……
107      }
       下面我们来分析SAVE_ALL和do_IRQ 函数中的struct pt_regs结构体。
文件名:arch/i386/kernel/entry.S
文件名:include/asm-i386/ptrace.h
84           #define SAVE_ALL /
85                  cld; /
86                  pushl %es; /
87                  pushl %ds; /
88                  pushl %eax; /
89                  pushl %ebp; /
90                  pushl %edi; /
91                  pushl %esi; /
92                  pushl %edx; /
93                  pushl %ecx; /
94                  pushl %ebx; /
95                  movl $(__USER_DS), %edx; /
96                  movl %edx, %ds; /
97                  movl %edx, %es;
26           struct pt_regs {
27                  long ebx;
28                  long ecx;
29                  long edx;
30                  long esi;
31                  long edi;
32                  long ebp;
33                  long eax;
34                  int xds;
35                  int xes;
36                  long orig_eax;
37                  long eip;
38                  int xcs;
39                  long eflags;
40                  long esp;
41                  int xss;
42           };
       从以上代码可以看出,SAVE_ALL中,依次将寄存器中的值压入栈中,其中最后一个压入的是pushl %ebx;我们观察struct pt_regs结构体,可以看出该结构体的第一个成员变量就是long ebx;然后就依次对应。其中long orig_eax;的所对应的push 语句为:push $i-256。是在jmp common_interrupt之前压入的。细心的读者可能还会产生另外一个疑问: struct pt_regs结构体中最后几个变量怎么在SAVE_ALL中没有对应的push。因为在进入中断服务程序之前,已经将这部分值压进栈中了,这是由硬件来完成的。
       从以上可以看出,在IA32平台上,参数的传递不是通过寄存器来实现的,而是通过栈帧来实现。但这并不是在Linux 操作系统上如此,而是所有的IA32平台都是如此,不管是何种操作系统,何种编译器,都得遵循前面所提及的规范。
       下面我们仍以中断为例,在内核中调用者是如何保护寄存器的。
48           fastcall unsigned int do_IRQ(struct pt_regs *regs)
49           {    
……
73           #ifdef CONFIG_4KSTACKS
……
92                         asm volatile(
93                                "       xchgl   %%ebx,%%esp      /n"
94                                "       call    __do_IRQ         /n"
95                                "       movl   %%ebx,%%esp      /n"
96                                : "=a" (arg1), "=d" (arg2), "=b" (ebx)
97                                : "0" (irq),   "1" (regs), "2" (isp)
98                                : "memory", "cc", "ecx"
99                         );
……
101         #endif
       上述代码中的第一个冒号表示输出值,第二个冒号表示是输入值,第三个冒号后表示将被破坏的部分,需要恢复的值。在该段代码中,将会调用__do_IRQ()函数,由于在调用完该函数后,还会用到ecx的值,所有需要保存将ecx的值。那么在__do_IRQ()函数中就可以毫无忌惮的使用ecx寄存器。
       从上例还可以发现,函数的输出值往往都存贮在寄存器eax中。第一个冒号后的"=a" (arg1)表示将变量arg1和eax绑定,也就是arg1 = eax。
五、      案例分析
前不久,笔者在逛优快云论坛时,发现不少人对如下题目展开了激烈的讨论。
#include<stdio.h>
low_to_up(char in);
 
void main()
{
       printf("%c/n",low_to_up('d'));
}
 
low_to_up(char in)
{
       char ch;
       if(in>='a' && in<='z')
              ch=in-'a'+'A';
       else
              return(ch);
}
       我们先在VC++ 6.0中反汇编这段代码:
1:    #include<stdio.h>
2:    low_to_up(char in);
3:
4:    void main()
5:    {
00401020   push        ebp
00401021   mov        ebp,esp
00401023   sub         esp,40h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea          edi,[ebp-40h]
0040102C   mov         ecx,10h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
6:        printf("%c/n",low_to_up('d'));
00401038   push        #64h      d的ASC码      (1处)
0040103A   call        @ILT+5(low_to_up) (0040100a)
此时把eax的值压栈,然后调用printf函数,所以此时打印的就是d。
0040103F   add         esp,4
00401042   push        eax           #              (5处)       
                              
00401043   push        offset string "%c/n" (0042001c)
00401048   call         printf (004010e0)
0040104D   add         esp,8
7:    }
00401050   pop         edi
00401051   pop         esi
00401052   pop         ebx
00401053   add         esp,40h
00401056   cmp         ebp,esp
00401058   call          __chkesp (00401160)
0040105D   mov         esp,ebp
0040105F   pop          ebp
00401060   ret
8:
9:    low_to_up(char in)
10:   {
00401080   push        ebp
  前一个栈帧的内容
……
64h
返回地址
ebp
   (ch)
   ……
当前栈的内容
这里的ebp+8的内容就是64h,此时eax就是64。
 
 
00401081   mov        ebp,esp

00401083   sub         esp,44h

00401086   push        ebx
00401087   push        esi
00401088   push        edi
00401089   lea          edi,[ebp-44h]
0040108C   mov         ecx,11h
00401091   mov         eax,0CCCCCCCCh
00401096   rep stos    dword ptr [edi]
11:       char ch;
12:       if(in>='a' && in<='z')
00401098   movsx       eax,byte ptr [ebp+8]       #   (2处)   
0040109C   cmp         eax,61h
0040109F   jl          low_to_up+36h (004010b6)
004010A1   movsx       ecx,byte ptr [ebp+8]
004010A5   cmp         ecx,7Ah
004010A8   jg          low_to_up+36h (004010b6)

此时的edx是计算机后的值,但eax的值仍旧还是没有变。然后存储到ebp-4中,就是ch中。
 

13:           ch=in-'a'+'A';

004010AA   movsx       edx,byte ptr [ebp+8]     #   (3处)  

004010AE   sub         edx,20h
004010B1   mov         byte ptr [ebp-4],dl
14:       else
004010B4   jmp         low_to_up+3Ah (004010ba)

15:           return(ch);     

004010B6   movsx       eax,byte ptr [ebp-4]

16:   }
004010BA   pop         edi     # 恢复寄存器的值,做返回处理  (7处)  
004010BB   pop         esi
004010BC   pop         ebx
004010BD   mov         esp,ebp
004010BF   pop         ebp
004010C0   ret
       从以上汇编可以看出,被调用者保存的寄存器在这里体现的酣畅淋漓。在被调用者函数中,根本不会管调用者保存的寄存器,直接使用。这里还体现了常用eax来存贮返回值。由于是使用eax做返回值,所以在5处,直接将eax压栈,所以该程序在VC++6.0中,打印”d”。由于在low_to_up()函数中,在比较过程中借用了eax寄存器。按正常的流程,在执行完if语句后,应该有一条恰当的退出语句,所以导致没有执行7处的汇编语句,把正确的值存储到eax中。从而阴差阳错的,在执行printf时,打印了错误的值。这有可能是VC++编译器的bug?
GCC的编译器似乎技高一筹,表现的更尽人意。下面我们来分析GCC反汇编后的代码(阅读这部分代码需了解GCC汇编语法):
       .file "csdn.c"
       .text

  前一个栈帧的内容
……
100
返回地址
ebp
   ……
esp-> (ch)
……
当前栈的内容
这里的ebp+8的内容就是100,此时ch被存储在ebp-8处。
 
 
.globl low_to_up
       .type       low_to_up, @function
low_to_up:
       pushl       %ebp
       movl       %esp, %ebp

       subl     $8, %esp

       movl       8(%ebp), %eax    #   (2处) 
       movb      %al, -1(%ebp)
       cmpb      $96, -1(%ebp)
       jle    .L2
       cmpb      $122, -1(%ebp)
       jg     .L2
       movzbl    -1(%ebp), %eax
       subb     $32, %al
此处运行完if语句的内容,但没有往ch里面的赋值
       movb      %al, -2(%ebp)   #   (3处)
       jmp .L3
.L2:
       movsbl    -2(%ebp),%eax
       movl       %eax, -8(%ebp)   #   (5处)
       jmp .L1
.L3:

.L1:

从ch中获取值,存储到eax中,然后返回

       movl       -8(%ebp), %eax     #   (4处)

       leave

       ret
       .size low_to_up, .-low_to_up
       .section   .rodata
.LC0:
       .string     "%c/n"
       .text
.globl main
       .type       main, @function
main:
       pushl       %ebp
       movl       %esp, %ebp
       subl    $8, %esp
       andl      $-16, %esp
       movl       $0, %eax
       subl     %eax, %esp
       movl       $100, (%esp)   #将d的值压入到栈中,然后调用low_to_up()函数    (1处)
       call     low_to_up
       movl       %eax, 4(%esp)    #   (6处)
       movl       $.LC0, (%esp)
       call     printf
       movl       $0, %eax
       leave
       ret
       .size main, .-main
       .section   .note.GNU-stack,"",@progbits
       .ident      "GCC: (GNU) 3.3.5 (Debian 1:3.3.5-13)"
       从以上代码可以看出,调用函数时,GCC编译器和VC++的编译器所做的处理差不多。但GCC把局部变量ch压在ebp-8处。然后用ebp-1/ebp-2做临时存储。我们从5处可以发现,在else语句中,编译器会将ebp-2的值压入到eax中,然后又将eax中的值在压入到ebp-8处,即ch的栈中。当返回时,会从ebp-8处取出ch的值,然后赋值给eax,退栈返回。在6处,将eax的值压栈,然后调用printf,输出值。由于在if条件中,并没有将值存储到ch中,所以在low_to_up函数返回时,当执行4处的汇编代码时,将ch处的随机值放到了eax中,所以该程序在Linux环境下,输出值为随机值。
       笔者猜测,如果在L3处的加上一段和L2处相同的代码,那么就可以获得正确的值。笔者然后就在代码中的if语句条件中加入了return 语句,GCC反汇编后的代码验证了笔者的猜测,代码如下:
low_to_up:
       pushl       %ebp
       movl       %esp, %ebp
       subl     $8, %esp
       movl       8(%ebp), %eax
       movb      %al, -1(%ebp)
       cmpb      $96, -1(%ebp)
       jle    .L2
       cmpb      $122, -1(%ebp)
       jg     .L2
       movzbl    -1(%ebp), %eax
       subb           $32, %al
       movb      %al, -2(%ebp)
       jmp .L3
.L2:
       movsbl    -2(%ebp),%eax
       movl       %eax, -8(%ebp)
       jmp .L1
.L3:
       movsbl    -2(%ebp),%eax
       movl       %eax, -8(%ebp)
.L1:
       movl       -8(%ebp), %eax
       leave
       ret
六、      总结
综上所述,当我们了解了函数调用规律后,结合汇编代码,在实际项目开发过程中可以定位一些让人比较琢磨不透的问题。也不要在项目中采用案例中的程序,给项目移植性造成困难。为什么在这个平台下好好的,怎么到了另外一个平台就出错了?
七、      参考文献
[1]  Andrew W.Appel,赵克佳等译 《现代编译原理C语言描述》 人民邮电出版社 2006
[2] Randal E.Bryant等,龚奕利等译 《深入理解计算机系统》 中国电力出版社 2004
[3] Agner Fog,云风译  《怎样优化Pentium系列处理器的代码》
http://www.codingnow.com/2000/download/cpentopt.htm
[4] Linus Torvalds, Linux内核源代码(2.6.10版本) http://www.kernel.org
 
使用vim+gtags阅读内核源码 这个功能就是gtags-cscope的Find this global definitionIT行业,技术更新换代快,人们都喜欢追求最新的技术,对老技术和产品多少有些轻视。这一点在编辑器中也是这样,cscope、gtags这些以tag为基础的产品,由于不是真的懂得C代码,在现在的编程环境中,已经比不上lsp了。但是,在C代码阅读的场景中,特别是内核源码阅读的场景中,这些技术还是有一席用武之地,在历史的长河中继续发光发热。 阅读详情

相关推荐

避开这些坑!DS18B20温度读取不准、时序混乱的5个常见问题与调试方法

本文深入解析DS18B20温度传感器在应用中常见的5个问题,包括温度读取不准、时序混乱等,并提供详细的调试方法。通过硬件设计优化、精准延时实现和数据解析技巧,帮助开发者有效解决温度值跳变、初始化失败等典型问题,提升OneWire协议下的温度测量精度。

weixin_29185493的博客 465

if 语句的3种比较

if 语句的3种比较

Wang3260584290的博客 1627

folium离线地图实战:从下载到部署的完整指南

本文提供了一份完整的folium离线地图实战指南。针对网络不稳定或内网环境下的地图应用需求,详细讲解了如何下载离线地图瓦片数据,并通过两种核心方法(直接替换URL与创建TileLayer)将其集成到folium中。文章还涵盖了部署优化、边界处理及资源本地化等关键步骤,帮助你构建完全脱离网络依赖的交互式地图应用。

weixin_29061041的博客 277

C语言使用技巧(二十五):函数参数中设置输出的参数【虽然简单,但是强大】应用布尔类型,判断非!

函数参数中可以设置输出的参数,这里就不用写return了,并且在调用函数时,可以更新当前的数据规则,使得程序继续向前执行(冒泡排序就是这种特定,感兴趣可以观看: C语言使用技巧(十二):如何找到冒泡排序之后最小数值在原数组中的索引),我们可以先来看看一个交换函数的例子: #include <stdio.h> #include <stdlib.h> void swap(int *a, int *b) { int c; c = *a; *a = *b;

源代码杀手的博客 940

保姆级避坑指南:用Docker在Ubuntu 22.04上部署Isaac Sim 5.1.0,搞定宇树G1仿真环境

本文提供了一份详细的保姆级避坑指南,帮助用户在Ubuntu 22.04上通过Docker部署Isaac Sim 5.1.0,并成功搭建宇树G1仿真环境。文章涵盖了环境准备、Docker与NVIDIA容器工具包的配置、Isaac Sim镜像的定制化拉取与配置,以及宇树G1仿真环境的实战调试,旨在帮助开发者高效解决部署过程中的常见问题。

weixin_33728268的博客 207

AutosarMCAL开发——基于TC3x系列时钟系统浅析

本文为博主个人学习总结记录,如有不正,欢迎指正。

qq_45583706的博客 889

struct ifreq学习和实例

一、struct ifreq结构体 这个结构定义在/usr/include/net/if.h,用来配置和获取ip地址,掩码,MTU等接口信息的。 /* Interface request structure used for socket ioctl's. All interface ioctl's must have parameter definitions which

Jalen_king 2万+

TC397 EB MCAL开发从0开始系列 之 [15.1] Fee配置实战 - 双扇区demo

TC397 EB MCAL Fee模块实战之双扇区配置使用

yy九歌的博客 1128

各类变量和零值的比较

目录 1.波尔变量和零值比较 2.整形变量和零进行比较 3.浮点变量和零值比较 4.指针和零值比较 1.波尔变量和零值比较 在C语言中本来是不存在bool类型的,但在C99标准中加入了bool类型。 在C99标准中引入了_Bool类型,在vs中将bool类型进行了宏定义,而_Bool类型使微软自己设计的一种类型,在别的编译器上不具有可移植性,故这里我们使用bool类型。 接下来我们进入我们的正题: 假设布尔变量名称为flag,那它与零值的比较在if语句中表示如下: if(f.

Rinki123456的博客 1132

跟踪雷达点迹处理

1,点迹定义: 假设研究对象为位置x和速度v,预测值是根据这一时刻的x值、v值来预测下一时刻的x、v;测量值是根据传感器也就是雷达得到的,因为各种误差的存在,这两个值都不准确,所以把他们加权融合,得到一个新的量—状态值。 点迹就是所谓的测量值,由雷达对同一个目标探测到的一系列点(假设目标静止,但由于误差的存在,会扫描出不止一个点),航迹对应状态值,是点迹和预测轨迹的加权融合。 2,雷达信号和点迹处理关系: 3,点迹航迹处理流程 4,仿真 clear all; RealData=G

wangjie36的博客 1万+

Kafka 入门篇(一)

初识 Kafka 什么是 Kafka Kafka 的基本术语 Kafka 的特性(设计原则) Kafka 的使用场景 Kafka 的消息队列 点对点模式的消息队列 发布-订阅模式的消息队列 Kafka 系统架构 核心 API Kafka 为何如此之快 broker 端配置 broker.id port zookeeper.connect log.dirs num.recovery.threads.per.data.dir auto.create.topics.enable topic默认配置 num.par

1238

ARM过程调用标准---APCS简介

原址:http://blog.youkuaiyun.com/skyflying2012/article/details/37510171 介绍 APCS,ARM 过程调用标准(ARM Procedure Call Standard),提供了紧凑的编写例程的一种机制,定义的例程可以与其他例程交织在一起。最显著的一点是对这些例程来自哪里没有明确的限制。它们可以编译自 C、 Pascal、也可以是用

vc66vcc的博客 768

openlayers:实现在连续更改时间维度时如何平滑地重新加载层 (附完整源码)

openlayers:实现在连续更改时间维度时如何平滑地重新加载层 (附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 349

通过内核源码看函数调用之前世今生(转载)

栈(Stack):一个有序的积累或堆积                                                               韦氏词典对每一位孜孜不倦的程序员来说,栈已深深的烙在其脑海中,甚至已经发生变异。栈可以用来传递函数参数、存储局部变量、以及存储返回值的信息、还可以用于保存寄存器的值以供恢复之用。       在X86平台上(又称之为IA32

嵌入式开发小站 822

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

linux内核第一个函数,通过内核源码看函数调用之前世今生 - 极光 - 优快云博客

通过内核源码看函数调用之前世今生作者:杨小华栈(Stack):一个有序的积累或堆积韦氏词典对每一位孜孜不倦的程序员来说,栈已深深的烙在其脑海中,甚至已经发生变异。栈可以用来传递函数参数、存储局部变量、以及存储返回值的信息、还可以用于保存寄存器的值以供恢复之用。在X86平台上(又称之为IA32),应用程序借用栈来支持函数(又称为过程)调用,变量的存储按后进先出(LIFO)的方式进行。一、栈帧布局在具...

weixin_27012835的博客 603

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

【Linux 内核源码分析】关于Linux内核源码目录结构

需要注意的是,Linux 内核的初始化代码不仅限于 init 目录,还分布在其他各个目录中。在 Linux 内核源码中,由于主要关注于内核本身的开发,usr 目录下的工具和库文件可能相对有限,不同的 Linux 发行版或用户空间环境可能会有自己独特的目录结构和文件组织方式。在 Linux 内核源码中,tools 目录是一个非常重要的目录,它包含了许多用于开发、调试和分析内核的工具。在 Linux 内核源码中,include 目录包含了大量的头文件,这些头文件定义了内核中使用的结构体、函数原型、常量等。

C/C++Linux、音视频、DPDK 3384

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

CentOS获取内核源码

CentOs 默认情况下是没有内核源码的,如果获取,并部署在usr/src/kernel/ 文件夹下的话,可以直接运行 yum install kernel yum install kernel-devel yum install kernel-headers 运行时可以在目录/usr/src/kernel/ 下直接运行,之后内核源码就会出现在/usr/src/kernel/目录下 如果是...

95年的程序狮 1205

看Linux内核源码

由于操作系统的内核贴近硬件,需要对计算机的体系结构有所了解,例如CPU的工作原理、接口特性和BIOS调用等。读者千万别因为这些望而却步,这里将介绍一些阅读Linux内核源码必备的技能。                                    获取内核     一般在Linux系统中的/usr/src/linux*.*.*(*.*.*代表的是内核版本,如2.4.23)目录下就是内核源代

Ursa_Major的专栏 1511

如何下载linux内核源码

本文介绍了从Linux内核官网获取内核源码的三种方法:1)通过官网首页直接下载最新发布版本;2)下载长期支持版本(LTS)如5.4.294;3)在/pub目录下查找历史版本如4.1.15。每种方法都配有详细的操作截图,包括点击"Latest Release"按钮、[tarball]链接以及/pub/linux路径导航等步骤说明,帮助用户快速定位并下载所需内核版本。文中还解释了内核版本号的组成结构,为开发者提供了全面的Linux内核源码获取指南。

不忘初心 2413

git获取linux内核源码及分支管理

1.    环境:ubuntu12.04. 2.    git安装:sudo apt-get install git-core ,安装完后查看版本如下:            3.   获取linux内核源码:     在kernel的官网上有三种版本,mainline,stable,longterm     mainline是主线版本,最新的,     stable是稳定版,

Terry 的博客 1万+

在ubuntu下获取对应内核源码命令

输入:apt-cache search linux-source //查看内核版本 输入:apt-get install linux-source-3.0.0 //获取对应版本的内核,默认安装在/usr/src目录下 见: Ubuntu下构建内核源码树的方法   ubuntu下编译本机模块是否需要下载内核源码呢? 答案是可以的。 dmesg | tai

haokaihaohe110的专栏 2万+

Linux内核源码包的安装及编译zz

http://my.donews.com/ecco2005/2006/11/24/eqbeyynfajhhygwgecappuwkaiftlrnpiouh/ Q:安装某些需要内核源码的软件时,为什么我已经将内核源码装在了/usr/src/linux下,仍然提示找不到内核源码? A:因为这些软件configure会通过检测/usr/src/linux/include/linux/version....

873

(超详细的Centos安装内核源码操作说明)一步一步安装Centos内核源码支持交叉编译

查看当前系统版本 [root@localhost w0412]# cat /etc/redhat-release CentOS Linux release 7.5.1804 (Core) [root@localhost w0412]# uname -r 3.10.0-862.el7.x86_64 下载内核源码包 使用wget直接下载 [root@localhost w0412]# wget --no-check-certificate https://vault.centos.org/7.9...

wgl307293845的博客 7034
上一篇: 考场安排---图的着色原理之运用
下一篇: 我的专题文章之一 --对变量的总结
normalnotebook
博客等级 码龄24年 647粉丝 38原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值