Linux学习笔记

1、linux头文件

#include <linux/***.h> 是在linux-2.6.29/include/linux下面寻找源文件。

#include <asm/***.h> 是在linux-2.6.29/arch/arm/include/asm下面寻找源文件。
#include <mach/***.h> 是在linux-2.6.29/arch/arm/mach-s3c2410/include/mach下面寻找源文件。
#include <plat/regs-adc.h>在linux-2.6.31_TX2440A20100510\linux-2.6.31_TX2440A\arch\arm\plat-s3c\include\plat
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。Hello World驱动要这一个文件就可以了
#include <linux/fs.h> //包含了文件操作相关struct的定义,例如大名鼎鼎的struct file_operations
                                  //包含了struct inode 的定义,MINOR、MAJOR的头文件。
#include <linux/errno.h> //包含了对返回值的宏定义,这样用户程序可以用perror输出错误信息。
#include <linux/types.h> //对一些特殊类型的定义,例如dev_t, off_t, pid_t.其实这些类型大部分都是unsigned int型通过一连串的typedef变过来的,只是为了方便阅读。
#include <linux/cdev.h> //对字符设备结构cdev以及一系列的操作函数的定义。//包含了cdev 结构及相关函数的定义。
#include <linux/wait.h> //等代队列相关头文件//内核等待队列,它包含了自旋锁的头文件
#include <linux/init.h>初始化头文件
#include <linux/kernel.h> 驱动要写入内核,与内核相关的头文件
#include <linux/slab.h>              //包含了kcalloc、kzalloc内存分配函数的定义。
#include <linux/uaccess.h>        //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/device.h>           //包含了device、class 等结构的定义
#include <linux/io.h>                  //包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/miscdevice.h>    //包含了miscdevice结构的定义及相关的操作函数。
#include <linux/interrupt.h>        //使用中断必须的头文件
#include <mach/irqs.h>             //使用中断必须的头文件
#include <asm/bitops.h>           //包含set_bit等位操作函数,实现Input子系统时可用。
#include <linux/semaphore.h>   //使用信号量必须的头文件
#include <linux/spinlock.h>       //自旋锁
#include <linux/sched.h>          //内核等待队列中要使用的TASK_NORMAL、TASK_INTERRUPTIBLE包含在这个头文件
#include <linux/kfifo.h>             //fifo环形队列
#include <linux/timer.h>           //内核定时器
#include <linux/input.h>           //中断处理 #include <linux/delay.h> 延时头文件 #include <asm/irq.h> 与处理器相关的中断 #include <linux/interrupt.h> 操作系统中断 #include <asm/uaccess.h> 与处理器相关的入口 //#include <asm/arch/regs-gpio.h> 与处理器相关的IO口操作 #include <mach/regs-gpio.h> 同上 //#include <asm/hardware.h> 与处理器相关的硬件 #include <mach/hardware.h> 同上 #include <linux/poll.h> 轮询文件 #include <linux/gpio.h> 操作系统相关的IO口文件 #include <stdio.h> 标准输入输出 #include <stdlib.h> 标准库 #include <unistd.h> #include <sys/ioctl.h> IO控制
头文件主目录include
头文件目录中总共有32个.h头文件。其中主目录下有13个,asm子目录中有4个,linux子目录中有10个,sys子目录中有5个。这些头文件各自的功能如下,具体的作用和所包含的信息请参见第14章。
<a.out.h>:a.out头文件,定义了a.out执行文件格式和一些宏。
<const.h>:常数符号头文件,目前仅定义了i节点中i_mode字段的各标志位。
<ctype.h>:字符类型头文件,定义了一些有关字符类型判断和转换的宏。
<errno.h>:错误号头文件,包含系统中各种出错号。(Linus从minix中引进的)。
<fcntl.h>:文件控制头文件,用于文件及其描述符的操作控制常数符号的定义。
<signal.h>:信号头文件,定义信号符号常量,信号结构以及信号操作函数原型。
<stdarg.h>:标准参数头文件,以宏的形式定义变量参数列表。主要说明了一个类型(va_list)和3个宏(va_start, va_arg和va_end),用于vsprintf、vprintf、vfprintf函数。
<stddef.h>:标准定义头文件,定义了NULL, offsetof(TYPE, MEMBER)。
<string.h>:字符串头文件,主要定义了一些有关字符串操作的嵌入函数。
<termios.h>:终端输入输出函数头文件,主要定义控制异步通信口的终端接口。
<time.h>:时间类型头文件,主要定义了tm结构和一些有关时间的函数原形。
<unistd.h>:Linux标准头文件,定义了各种符号常数和类型,并声明了各种函数。如,定义了__LIBRARY__,则还包括系统调用号和内嵌汇编_syscall0()等。
<utime.h>:用户时间头文件,定义了访问和修改时间结构以及utime()原型。
(1)体系结构相关头文件子目录include/asm
这些头文件主要定义了一些与CPU体系结构密切相关的数据结构、宏函数和变量。共4个文件。
<asm/io.h>:I/O头文件,以宏的嵌入汇编程序形式定义对I/O端口操作的函数。
<asm/memory.h>:内存拷贝头文件,含有memcpy()嵌入式汇编宏函数。
<asm/segment.h>:段操作头文件,定义了有关段寄存器操作的嵌入式汇编函数。
<asm/system.h>:系统头文件,定义了设置或修改描述符/中断门等的嵌入式汇编宏。
(2)Linux内核专用头文件子目录include/linux
<linux/config.h>:内核配置头文件,定义键盘语言和硬盘类型(HD_TYPE)可选项。
<linux/fdreg.h>:软驱头文件,含有软盘控制器参数的一些定义。
<linux/fs.h>:文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)。
<linux/hdreg.h>:硬盘参数头文件,定义访问硬盘寄存器端口、状态码和分区表等信息。
<linux/head.h>:head头文件,定义了段描述符的简单结构,和几个选择符常量。
<linux/kernel.h>:内核头文件,含有一些内核常用函数的原形定义。
<linux/mm.h>:内存管理头文件,含有页面大小定义和一些页面释放函数原型。
<linux/sched.h>: 调度程序头文件,定义了任务结构task_struct、初始任务0的数据,
以及一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。
<linux/sys.h>:系统调用头文件,含有72个系统调用C函数处理程序,以"sys_"开头。
<linux/tty.h>:tty头文件,定义了有关tty_io,串行通信方面的参数、常数。
(3)系统专用数据结构子目录include/sys
<sys/stat.h>: 文件状态头文件,含有文件或文件系统状态结构stat{}和常量。
<sys/times.h>:定义了进程中运行时间结构tms以及times()函数原型。
<sys/types.h>:类型头文件,定义了基本的系统数据类型。
<sys/utsname.h>:系统名称结构头文件。

<sys/wait.h>:等待调用头文件,定义系统调用wait()和waitpid()及相关常数符号


2

file_operations结构体详细分析

struct module *owner

 第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针.
 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 
THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.这个宏比较复杂,在进行简单学习操作的时候,一般初始化为THIS_MODULE。


loff_t (*llseek) (struct file * filp , loff_t  p,  int  orig);
(指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量;参数orig为对文件定位
的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2))
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示.
如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).

ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t *  p);
(指针参数 filp 为进行读取信息的目标文件,指针参数buffer 为对应放置信息的缓冲区(即用户空间内存地址),
参数size为要读取的信息长度,参数 p 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值)
这个函数用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败.
 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).

ssize_t (*aio_read)(struct kiocb *  , char __user *  buffer, size_t  size ,  loff_t   p);
可以看出,这个函数的第一、三个参数和本结构体中的read()函数的第一、三个参数是不同 的,
异步读写的第三个参数直接传递值,而同步读写的第三个参数传递的是指针,因为AIO从来不需要改变文件的位置。
异步读写的第一个参数为指向kiocb结构体的指针,而同步读写的第一参数为指向file结构体的指针,每一个I/O请求都对应一个kiocb结构体);
初始化一个异步读 -- 可能在函数返回前不结束的读操作.如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).
(有关linux异步I/O,可以参考有关的资料,《linux设备驱动开发详解》中给出了详细的解答)

ssize_t (*write) (struct file *  filp, const char __user *   buffer, size_t  count, loff_t * ppos);
(参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,
ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界)
发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
(注:这个操作和上面的对文件进行读的操作均为阻塞操作)

ssize_t (*aio_write)(struct kiocb *, const char __user *  buffer, size_t  count, loff_t * ppos);
      初始化设备上的一个异步写.参数类型同aio_read()函数;

int (*readdir) (struct file *  filp, void *, filldir_t);
对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.

unsigned int (*poll) (struct file *, struct poll_table_struct *);
(这是一个设备驱动中的轮询函数,第一个参数为file结构指针,第二个为轮询表指针)
这个函数返回设备资源的可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位“或”结果。
每个宏都表明设备的一种状态,如:POLLIN(定义为0x0001)意味着设备可以无阻塞的读,POLLOUT(定义为0x0004)意味着设备可以无阻塞的写。
(poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞.
 poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 
如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.
(这里通常将设备看作一个文件进行相关的操作,而轮询操作的取值直接关系到设备的响应情况,可以是阻塞操作结果,同时也可以是非阻塞操作结果)

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
(inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值, 和传递给 open 方法的相同参数.
cmd 参数从用户那里不改变地传下来, 并且可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针.
如果调用程序不传递第 3 个参数, 被驱动操作收到的 arg 值是无定义的.
因为类型检查在这个额外参数上被关闭, 编译器不能警告你如果一个无效的参数被传递给 ioctl, 并且任何关联的错误将难以查找.)
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表.
 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.

int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
(如果想对这个函数有个彻底的了解,那么请看有关“进程地址空间”介绍的书籍)

int (*open) (struct inode * inode , struct file *  filp ) ;
(inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构;
但是filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息)
 尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
与open()函数对应的是release()函数。

int (*flush) (struct file *);
flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作.
这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用;
 SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.

int (*release) (struct inode *, struct file *);
release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数:
void release(struct inode inode,struct file *file),release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。
    在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.

int(*synch)(struct file *,struct dentry *,int datasync);
刷新待处理的数据,允许进程把所有的脏缓冲区刷新到磁盘。


int (*aio_fsync)(struct kiocb *, int);
 这是 fsync 方法的异步版本.所谓的fsync方法是一个系统调用函数。系统调用fsync
把文件所指定的文件的所有脏缓冲区写到磁盘中(如果需要,还包括存有索引节点的缓冲区)。
相应的服务例程获得文件对象的地址,并随后调用fsync方法。通常这个方法以调用函数__writeback_single_inode()结束,
这个函数把与被选中的索引节点相关的脏页和索引节点本身都写回磁盘。

int (*fasync) (int, struct file *, int);
这个函数是系统支持异步通知的设备驱动,下面是这个函数的模板:
static int ***_fasync(int fd,struct file *filp,int mode)
{
    struct ***_dev * dev=filp->private_data;
    return fasync_helper(fd,filp,mode,&dev->async_queue);//第四个参数为 fasync_struct结构体指针的指针。
//这个函数是用来处理FASYNC标志的函数。(FASYNC:表示兼容BSD的fcntl同步操作)当这个标志改变时,驱动程序中的fasync()函数将得到执行。
}
此操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述.
这个成员可以是NULL 如果驱动不支持异步通知.

int (*lock) (struct file *, int, struct file_lock *);
lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作;
 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).

ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个.
例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中.
这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.[10]

int (*check_flags)(int)
这个方法允许模块检查传递给 fnctl(F_SETFL...) 调用的标志.

int (*dir_notify)(struct file *, unsigned long);
这个方法在应用程序使用 fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_notify.

一般情况下,进行设备驱动程序的设计只是比较注重下面的几个方法:
struct file_operations ***_ops={
 .owner =  THIS_MODULE,
 .llseek =  ***_llseek,
 .read =  ***_read,
 .write =  ***_write,
 .ioctl =  ***_ioctl,
 .open =  ***_open,
 .release = ***_release, 
};


S3C2440的LED驱动


驱动程序:

/*whjled.c*/

#include<mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>

#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/atomic.h>
#include <asm/unistd.h> 

#define LED_ON  1
#define LED_OFF 0
#define DEVICE_NAME "whjled"
#define LED_MAJOR 98

static unsigned long led_table [] = 
{
    S3C2410_GPB(5),
    S3C2410_GPB(6),
    S3C2410_GPB(7),
    S3C2410_GPB(8),
}; 

static unsigned int led_cfg_table [] = 
{
    S3C2410_GPIO_OUTPUT,
    S3C2410_GPIO_OUTPUT,
    S3C2410_GPIO_OUTPUT,
    S3C2410_GPIO_OUTPUT,
}; 

static int s3c2440_leds_ioctl(
       struct inode *inode,
       struct file *file,
       unsigned int cmd,
       unsigned long arg)
{
    if(arg>3)
    {
        printk("Led's number error,pleasecheck!");    
        return -EINVAL;
    }
    switch(cmd) 
    {
        case LED_ON:
        s3c2410_gpio_setpin(led_table[arg],0);//led low light
        return 0;
        case LED_OFF:
        s3c2410_gpio_setpin(led_table[arg], 1);
        return 0;
        default:
        return -EINVAL;
    }


static struct file_operations dev_fops = 
{
    .owner = THIS_MODULE,
    .ioctl = s3c2440_leds_ioctl,
};

static int __init dev_init(void)
{
     int ret;
     int i;
     ret = register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops);
     
     if(ret<0)
     {
         printk(DEVICE_NAME"uninitialized\n");
         return ret;
     }
     for (i = 0; i < 4; i++) 
     {
         s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);
         s3c2410_gpio_setpin(led_table[i], 1);
     }
     
     printk (DEVICE_NAME"initialized\n");
     return 0;
}

static void __exit dev_exit(void)
{
    unregister_chrdev(LED_MAJOR,DEVICE_NAME);


module_init(dev_init);
module_exit(dev_exit);

驱动文件对应的Makefile为:

obj-m:=whjled.o
PWD:=$(shell pwd)
KDIR:=/home/linux-2.6.32.2c

all:
    $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:
    rm -rf *.o *.ko *.mod.c *.mod.o *.symvers

将whjled.c与Makefile放在同一文件夹内(linux环境)执行make,生成whjled.ko。将whjled.ko拷贝至根文件系统的tmp文件夹下(我使用的是nfs根文件系统)。

在/tmp文件夹下加载驱动模块:输入insmod whjled.ko,若创建成功,可通过cat /proc/devices查看到设备whjled与其主设备号98.

在/dev文件夹下建立设备节点:输入mknod whjled c 98 1,若成功,可通过ls /dev查看到设备文件
测试程序如下:
/*cpp.c*/
#include <stdio.h>
#include <stdlib.h> 
#include <unistd.h> 

#define LED_ON   1
#define LED_OFF  0
#define LED_DEVICE  "/dev/whjled" 

int main(int argc,char **argv)
{
    int fd,led_num;
    
    fd = open(LED_DEVICE,0);
    
    if(fd < 0)
    {
        printf("can't open/dev/leds!\n");
        exit(0);
    }   

    led_num = atoi(argv[1]);

    if(!(strcmp(argv[2],"on")))
        ioctl(fd,LED_ON,led_num);
    else 
    {
        if(!(strcmp(argv[2],"off")))
              ioctl(fd,LED_OFF,led_num);
    }
    exit(0);
}
将驱动程序放在linux环境内,执行arm-linux-gcc-static cpp.c -o cpp,可得到可执行文件cpp。将cpp拷贝至根文件系统的/tmp文件夹下,就可通过
./cpp 0 on

./cpp 0 off

./cpp 1 on

./cpp 1 off
.....

./cpp 3 off
操作 LED 。
注意:

1、注册设备的时候,有两种方式:一种是使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops),LED_MAJOR为定义的主设备号,DEVICE_NAME为定义的设备名称,dev_fops为定义的文件操作结构体。使用该函数向系统注册字符型设备驱动程序,主设备号LED_MAJOR自己定义,如该值为0则系统自动分配主设备号;另一种是使用misc_register(&misc)。如果是非标准设备则使用 misc_register,即一些字符设备不符合预先确定的字符设备范畴,就用这种方式,它固定使用主设备号10注册,如果多个设备次设备号不同。

2、使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时,如果有多个设备使用该函数注册驱动程序,LED_MAJOR不能相同,否则几个设备都无法注册(我已在友善的板子上验证)。如果模块使用该方式注册并且LED_MAJOR为0(自动分配主设备号),使用insmod命令加载模块时会在终端显示分配的主设备号和次设备号,在/dev目录下建立该节点,比如设备leds,如果加载该模块时分配的主设备号和次设备号为253和0,则建立节点:mknodleds c 253 0。使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时都要手动建立节点,否则在应用程序无法打开该设备。

3、由于我使用的是静态链接的根文件系统所以要加“-static”选项。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值