Linux内核

文章目录

  • Linux内核代码结构
  • 模块化编程
    • 编译加载查看命令
    • 符号导出
    • 加载模块时给模块传参数
      • 在程序中声明传递的参数
      • 在加载模块时跟传入的参数
  • Sysfs
  • 设备分类
    • 设备号
      • 注册/注销设备号
    • 字符设备
    • gpio操作
      • 头文件:
      • 函数原型
    • ioctl
      • 应用层系统调用
      • 驱动层调用
      • cmd参数

Linux内核代码结构

  • init
  • kernel(内核管理核心代码)
  • mm(memory management内核管理代码,处理器体系结构相关的代码仿真arch/*/mm)
  • ipc(IPC进程间通信)
  • lib(内核的库代码,体系结构相关的代码放在arch/*/lib)
  • fs(file system文件系统)
  • net(网络协议代码)
  • drivers(系统中驱动程序代码)
    • char(字符设备)
    • usb(USB设备)
    • block(块设备)
    • pci(PCI总线)
    • i2c
    • spi
  • include(Linux头文件)
    • asm-(体系结构相关头文件)
    • linux(Linux kernel core 头文件)
  • scripts(编译配置脚本文件)
  • sound(声卡驱动程序)
  • Makefile(顶层Makefile文件)

以上平台无关-------------------------以下平台相关

  • arch(CPU架构相关代码,每个目录一个体系结构)
    • i386
    • arm
      • kernel
      • mm
      • lib(体系结构相关的库代码)
    • x86
    • ppc
    • m68k
    • sh

模块化编程

module_initmodule_exit宏将函数放在内核的某一段

module_init(initFunc);//模块的入口:完成模块的加载
module_exit(exitFunc);//模块的出口:完成模块的卸载

编译加载查看命令

  • make 编译 生成 .ko模块文件
  • lsmod 显示模块
  • insmod/rmmod 安装/卸载模块
  • dmesg 打印log信息

linux内核源码默认装在/lib/modules/$(shell uname -r)/build目录下

把一个模块独立于内核在外面编译再动态加载进去

make ... modules

符号导出

符号:全局变量和函数

Linux内核采用的是以模块化形式管理内核代码。内核中的每个模块相互之间是相互独立的,也就是说A模块的全局变量和函数,B模块是无法直接访问的。如果想要访问别的模块的变量或函数,就需要用到全局符号表。

Linux内核会把符号放在一个表里面,在编译的时候回去表里面找对应的全局变量和函数的符号。

Ubuntu中

  • Linux内核的全局符号表在/usr/src/linux-headers-XXXXX-generic/Module.symvers
    XXXXX是内核版本

相当于可以在多个文件中共享的变量或函数,在一个文件中定义,可以在其他文件中使用

在程序中使用下面这个宏导出变量或函数符号,即便是static限定(作用域为当前程序文件)的变量也可以被导出

EXPORT_SYMBOL(变量或函数);

加载模块时给模块传参数

在程序中声明传递的参数

#include 
  • 原型:

    module_param(name,type,perm);//声明传类型为type的参数name
    module_param_arry(name,type,num_point,perm);//传一个元素个数为num_point的数组,数组元素类型为type
    module_param_string (name,string,len,perm);//传长度为len的字符串
    
  • 参数:

    • name 用来接收参数的变量名
    • type 参数的数据类型
      • bool 布尔值(true/false)
      • invbool bool的反值,例如赋值为true,实际值为false
      • charp 字符指针类型,内核为用户提供的字符串分配内存,并设置此指针保存其首地址
      • int 整型
      • long 长整型
      • short 短整型
      • uint 无符号整型
      • ulong 无符号长整型
      • ushort 无符号短整型
    • perm 指定参数访问权限

在加载模块时跟传入的参数

加入在程序中声明传递的参数为 var

module_param(var,int,0664);

在命令行加载模块时传参

insmod hello.ko var=1

Sysfs

Sysfs:内核给一些重要的资源创建的目录或者文件,每个模块会在/sys/module下创建个同名的文件夹

文件夹里的parameters目录下就放着我们传入的参数

设备分类

  • 字符设备
    以字节为单位来操作数据。
    比如:键盘、鼠标、显示器都等是字符设备。
    字符设备的驱动程序,就称为"字符设备驱动程序”。
  • 块设备
    块设备存储的数据量往往非常答,为了提高读写效率,都是以块(1024字节)为单位来操作数据。
    比如:电脑硬盘、移动硬盘、u盘等,凡是涉及大量数据存储的,都是以块为单位来操作数据的,都是块设备。块设备的驱动程序,就成为"块设备驱动程序”。
  • 网络设备

Linux内核_第1张图片

设备号

用来区分设备的整型数编号

  • 无符号整型值
    • 高12位∶主设备号
    • 低20位:次设备号
  • 查看设备号
    • cat /proc/devices

/proc目录:processing进程信息

注册/注销设备号

int register_chrdev_region(dev_t from,unsigned count,const char *name);
  • 功能:注册一系列设备号(主设备号只有一个,但是可以有一系列次设备号)

  • 参数:

    • from:设备号的起始值
    • count:申请多少个设备号
    • name:设备号的名字

    申请从from到from+count-1的设备号

int unregister_chrdev_region(dev_t from,unsigned count);
  • 功能:注销设备号

  • 参数:同上

    注销从from到from+count-1的设备号

例子:

#include 
#include 
#include 
#include 
static int major=250;
static int minor=0;
static dev_t devno;
static int hello_init(void)
{
	devno=MKDEV(major,minor);//宏定义:用主设备号和次设备号生成设备号,实际就是(major<<20)|minor
    int result=register_chrdev_region(devno,1,"test");//注册设备号
    ...
}
static void hello_exit(void)
{
    unregister_chrdev_region(devno,1);//注销设备号
    ...
}
module_init(hello_init);
module_exit(hello_exit);

字符设备

对于每一个字符设备,内核使用一个结构体cdev来维护。

块设备叫gn_disk

网络设备叫net_device

Linux内核_第2张图片

gpio操作

头文件:

#include
#include 
#include 
# include 

函数原型

int gpio_request(unsigned gpio, const char *label);
  • 函数功能:CPU的任何一个GPIO引脚硬件资源对于Linux内核来说都是一种宝贵的资源,如果某个内核程序要想访问这个GPIO引脚资源,首先必须想Linux内核申请资源(类似malloc)
  • 参数说明:
    • gpio:GPIO引脚硬件在linux内核中的软件编号,也就是对于任何一个GPIO引脚,linux内核都给分配一个唯一的软件编号(类似GPIO引脚的身份证号)
  • label:给申请的硬件GPIO引脚指定的名称,随便取。
    返回值:看内核大神的代码如何判断即可,照猫画虎
void gpio_free(unsigned gpio);
  • 函数功能:内核程序如果不再使用访问GPIO硬件资源记得要将硬件资源归还给linux内核,类似free。
  • 参数:
    gpio:要释放的GPIO硬件资源对应的软件编号
int gpio_direction_output(unsigned gpio, int value);
  • 函数功能:配置GPIO引脚为输出功能,并且输出一个value值(1高电平/0低电平)
  • 参数:
    • gpio:GPIO硬件对应的软件编号
    • value:输出的值
int gpio_direction_input(unsigned gpio);
  • 函数功能:配置GPIO为输入功能
int gpio_set_value(unsigned gpio, int value);
  • 函数功能:设置GPIO引脚的输出值为value(1:高/0:低),前提是必须首先将GPIO配置为输出功能
int gpio_get_value(unsigned gpio);
  • 函数功能:获取GPIO引脚的电平状态,返回值就是引脚的电平状态(返回1:高电平;返回0:低电平),此引脚到底是输入还是输出没关系!

ioctl

ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。

Linux内核_第3张图片

应用层系统调用

#include 
int ioctl(int fd,unsigned long cmd,...);

参数:

  • fd:打开设备文件的时候获得的文件描述符
  • cmd:第二个参数:给驱动层传递的命令,需要注意的时候,驱动层的命令和应用层的命令一定要统一
  • 第三个参数:...在c语言中,很多时候都被理解成可变参数。

返回值:

  • 成功:0
  • 失败:-1,同时设置errno

驱动层调用

long (*unlocked_ioctl)(struct file* file,unsigned int cmd,unsigned long arg);

参数:

  • file::vfs层为打开字符设备文件的进程创建的结构体,用于存放文件的动态信息
  • cmd:用户空间传递的命令,可以根据不同的命令做不同的事情
  • 第三个参数:用户空间的数据,这个数据可能是一个地址值(用户空间传递的是一个地址),也可能是一个数值,也可能没值

返回值:

  • 成功:0
  • 失败:带错误码的负值

cmd参数

Linux内核_第4张图片

  • 设备类型:类型或叫幻数,代表一类设备,一般用一个字母或者1个8bit的数字
  • 序列号:代表这个设备的第几个命令
  • 方向:表示是由内核空间到用户空间,或是用户空间到内核空间,只读,只写,读写,其他
  • 数据尺寸:表示需要读写的参数大小

你可能感兴趣的:(计算机,linux,网络,运维)