- [X] Porting to linux kernel 3.13
- [X] Porting to linux kernel 3.14
- [X] Portng to linux kernel 3.18
- [X] Porting to linux kernel 4.7
- [X] Porting to linux kernel 4.8
每个包含一段内存区域,这段内存是全局的,且是可持久的。
FIFO(First In First Out)设备,像管道一样工作。
- scullsingle 每次只允许一个进程访问该设备
- scullpriv 对每个虚拟终端,都是私有的。
- sculluid, scullwuid 可被打开多次, 但一次只能由一个用户打开
/*
* Representation of scull quantum sets.
*/
struct scull_qset {
void **data;
struct scull_qset *next;
};
struct scull_dev {
struct scull_qset *data; /* Pointer to first quantum set */
int quantum; /* the current quantum size */
int qset; /* the current array size */
unsigned long size; /* amount of data stored here */
unsigned int access_key; /* used by sculluid and scullpriv */
struct semaphore sem; /* mutual exclusion semaphore */
struct cdev cdev; /* Char device structure */
};
CONFIG_DEBUG_KERNEL
打开此项后,就可以使其他的一些调试选项可见。CONFIG_DEBUG_SLAB
检测内存越界或是未初始化等错误。CONFIG_DEBUG_PAGEALLOC
当释放时,完整的页会从内核地址空间删除。CONFIG_DEBUG_SPINLOCK
检测对一个未初始化的spinlock的操作。CONFIG_DEBUG_SPINLOCK_SLEEP
检测当拥有一个spinlock时,还试图进入sleep的代码。CONFIG_INIT_DEBUG
检测试图访问__init(或__initdata)标记的代码。CONFIG_DEBUG_INFO
将完整的调试信息统进内核。特别是想使用gdb调试内核时,非常有用。CONFIG_MAGIC_SYSRQ
启用”magic SysRq”键。CONFIG_DEBUG_STACKOVERFLOW
CONFIG_DEBUG_STACK_USAGE
有助于跟踪内核栈溢出的问题。CONFIG_KALLSYMS
将内核符号信息编译进内核。发生oops时,打印调用栈时可以看到函 数符号信息,而不是一些十六进制的数字。CONFIG_IKCONFIG
CONFIG_IKCONFIG_PROC
打开后,会将完整的内核配置状态编进内核,这样可以通过/proc/目 录下的一些文件查看。CONFIG_ACPI_DEBUG
ACPI调试信息。CONFIG_DEBUG_DRIVER
打开驱动核心代码的日志输出。CONFIG_SCSI_CONSTANTS
SCSI驱动调试时使用。CONFIG_INPUT_EVBUG
打开输入事件的一些输出。调试输入设备驱动时使用CONFIG_PROFILING
性能调优或跟踪内核Hang住等相关问题。
printk 函数将消息发送到一个环形Buffer:__LOG_BUF_LEN字节长,大 小从4 KB到1 MB大小。可以通过syslog或读取/proc/kmsg来获取该buffer 中的信息。其中,读取/proc/kmsg的方式会消耗掉buffer中的数据。
有时,同一个Log信息打印太多,会影响Log分析,可以通过
printk_ratelimit()
来控制一条Log的打印次数。 使用方法一般如下所示:
if (printk_ratelimit( ))
printk(KERN_NOTICE "The printer is still on fire\n");
可以通过如下两种方法修改 printk_ratelimit()
的行为:
- 修改”/proc/sys/kernel/printk_ratelimit” 重新enable消息打印前, 等待的时间。
- “/proc/sys/kernel/printk_ratelimit_burst”: 在启用rate_limit前, 允许同一份Log打印的次数。
int print_dev_t(char *buffer, dev_t dev);
char *format_dev_t(char *buffer, dev_t dev);
- 字符设备驱动程序框架
- Miscellaneous Character Drivers
- iotcl函数
- 常见函数
copy_from_user __copy_from_user //(no access check) copy_to_user __copy_to_user //传输小数据如1,2, 4, 8字节的数据 __put_user() put_user() __get_user() get_user() //能力函数 #include <linux/sched.h> int capable(int capability);
基本调用步骤:
- 初始化一个等待队列头:
init_waitqueue_head(&ret->wait_queue);
注: 判断队列是否为空:
waitqueue_active(...)
, 返回false即表示队列为空. - 等待某个条件发生:
wait_event(...)
或wait_event_timeout(...)
- 唤醒队列
wake_up_all(...)
- 初始化一个等待队列头:
手动睡眠
- 初始化一个等待队列项
DEFINE_WAIT(my_wait);
或者
wait_queue_t my_wait; init_wait(&my_wait);
- 将等待队列项加入到队列中
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);
- 调用
prepare_to_wait
后,可以调用schedule()
- 当schedule返回,执行清理工作。
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
- 初始化一个等待队列项
- 内核变量——Jiffies
jiffies与struct timeval, struct timespec之间的转换:
#include <linux/time.h> unsigned long timespec_to_jiffies(struct timespec *value); void jiffies_to_timespec(unsigned long jiffies, struct timespec *value); unsigned long timeval_to_jiffies(struct timeval *value); void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
读取64-bit的jiffies值:
#include <linux/jiffies.h> u64 get_jiffies_64(void);
turn a wall-clock time into a jiffies value:
#include <linux/time.h> unsigned long mktime (unsigned int year, unsigned int mon, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec);
获取绝对时间:
#include <linux/time.h> void do_gettimeofday(struct timeval *tv); //获取当前时间 #include <linux/time.h> struct timespec current_kernel_time(void);
- 延迟运行
- Busy Waiting
The HZ symbol specifies the number of clock ticks generated per second.
while (time_before(jiffies, j1)) cpu_relax( );
- schedule
while (time_before(jiffies, j1)) { schedule( ); }
- Timeouts
#include <linux/wait.h> long wait_event_timeout(wait_queue_head_t q, condition, long timeout); long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout); #include <linux/sched.h> signed long schedule_timeout(signed long timeout); set_current_state(TASK_INTERRUPTIBLE); schedule_timeout (delay);
- 短延时
//busy waiting #include <linux/delay.h> void ndelay(unsigned long nsecs); void udelay(unsigned long usecs); void mdelay(unsigned long msecs); //no busy waiting void msleep(unsigned int millisecs); unsigned long msleep_interruptible(unsigned int millisecs); void ssleep(unsigned int seconds)
- 内核定时器与延时
#include <linux/timer.h> struct timer_list { /* ... */ unsigned long expires; void (*function)(unsigned long); unsigned long data; }; void init_timer(struct timer_list *timer); struct timer_list TIMER_INITIALIZER(_function, _expires, _data); void add_timer(struct timer_list * timer); int del_timer(struct timer_list * timer); int mod_timer(struct timer_list *timer, unsigned long expires);//update timer //Works like del_timer, but also guarantees that when it returns, the timer //function is not running on any CPU. int del_timer_sync(struct timer_list *timer); /* Returns true or false to indicate whether the timer is currently scheduled to run by reading one of the opaque fields of the structure. */ int timer_pending(const struct timer_list * timer);
- 下半部机制之微线程
数据结构:
#include <linux/interrupt.h> struct tasklet_struct { /* ... */ void (*func)(unsigned long); unsigned long data; }; void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); DECLARE_TASKLET(name, func, data); DECLARE_TASKLET_DISABLED(name, func, data);
- 下半部机制之工作队列
struct workqueue_struct *create_workqueue(const char *name);//为每个CPU创建一个内核线程 struct workqueue_struct *create_singlethread_workqueue(const char *name);//只创建一个内核线程 // work_struct DECLARE_WORK(name, void (*function)(void *), void *data); INIT_WORK(struct work_struct *work, void (*function)(void *), void *data); PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data); //修改work_struct //submit work int queue_work(struct workqueue_struct *queue, struct work_struct *work); int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay); //cancel pending workqueue entry int cancel_delayed_work(struct work_struct *work); //make sure the work function is not running //anywhere in the system after cancel_delayed_work returns 0 void flush_workqueue(struct workqueue_struct *queue); //get rid of a workqueue void destroy_workqueue(struct workqueue_struct *queue);
- 共享工作队列
大部分情况下,我们不需要创建自己的工作队列,可以将工作项提交 到默认的工作队列中。
int schedule_work(struct work_struct *work); //submit work int schedule_delayed_work(struct work_struct *work, unsigned long delay); //cancel delayed work int cancel_delayed_work(struct work_struct *work); //flush the shared workqueue void flush_scheduled_work(void);
- Busy Waiting
- it simulates conversations with real remote hosts in order to better demonstrate the task of writing a network driver.
- it supports only IP traffic.
- The snull module creates two interfaces.
- How a host sees its interfaces
- possible configuration
/etc/networks
network ip snullnet0 192.168.0.0 snullnet1 192.168.1.0 /etc/hosts
ip host 192.168.0.1 local0 192.168.0.2 remote0 192.168.1.2 local1 192.168.1.1 remote1
struct net_device
描述一个网络接口。头文件: <linux/netdevice.h>struct net_device *alloc_netdev(int sizeof_priv, const char *name, void (*setup)(struct net_device *));
- 注册网络设备
for (i = 0; i < 2; i++) if ((result = register_netdev(snull_devs[i]))) printk("snull: error %i registering device \"%s\"\n", result, snull_devs[i]->name);
netif_start_queue/netif_stop_queue
标记Driver是否可以传输数据包。/* If you must disable packet transmission from anywhere other than your hard_start_xmit function (in response to a reconfiguration request, perhaps), the function you want to use is: */ void netif_tx_disable(struct net_device *dev); /* This function is just like netif_start_queue, except that it also pokes the networking system to make it start transmitting packets again. */ void netif_wake_queue(struct net_device *dev);
- 数据传输与接收
当可以传输数据时,内核会调用
ndo_start_transmit
将数据放到队 列中。netif_rx(skb);//hand off the socket buffer to the upper layers. netif_receive_skb(skb); //feed packets to the kernel, used in poll mode
- chagnes in link state
void netif_carrier_off(struct net_device *dev); void netif_carrier_on(struct net_device *dev); int netif_carrier_ok(struct net_device *dev);
- ioctl
Any ioctl command that is not recognized by the protocol layer is passed to the device layer. These device-related ioctl commands accept a third argument from user space, a
struct ifreq *
.
netif_napi_add()
把网络设备与napi虚拟设备绑定- 使能和禁用
netif_enalbe()
netif_disable()
- 调度NAPI
netif_schedule()
- 不同的网卡驱动都会为网络设备定义自己的结构体。有的是自己的结
构体中即包含
net_device
和 napi,有的是把自己的结构体放到net_device
的priv部分。 - 实现NAPI的轮询函数
rx_pool(struct napi_struct *napi, int weigh)
/* @param wight: 一次允许处理的报文最大数量 **/ int rx_pool(struct napi_struct *napi, int weigh) { int rx_cnt = 0; struct sk_buff *skb; while (rx_cnt < weigh) { skb = netdev_alloc_skb(); copy_skb_from_hw(skb); rx_cnt++; netif_receive_skb(skb); /*送内核协议栈处理*/ } if (rx_cnt < weigh) { /* 处理完了所有包,使能中断 */ napi_complete(napi); enable_rx_irq(); } return rx_cnt; }
- 把网络设备与NAPI相关联
netif_napi_add(netdev, napi, rx_pool, weigh);
- 注册网卡的收包中断
request_irq(irq, &rx_irq_handle, 0, netdev->name, netdev);
在处理函数中,
static irqreturn_t rx_irq_handle(int irq, void *data) { struct net_device *netdev = data; struct napi_struct *napi = netdev_priv(netdev)->napi; disable_rx_irq(); napi_schedule(napi); }
how a PCI driver can find its hardware and gain access to it.
Each PCI peripheral is identified by a bus number, a device number and a function number. In Linux, it support domain. domain(16bits) + bus(8bits) + device(5bits) + function(3bits)
每当一个外围设备都会响应三种地址的查询:
- Memory Locations
- I/O ports
- Configuration Registers
其中对于处于同一个PCI总线上的设备,前两个地址是一样的.
三个或五个PCI寄存器标识一个设备: vendorID, deviceID,class. additional registers: subsystem vendorID, subsystem deviceID.
struct pci_device_id {
__u32 vendor;
__u32 device;
//Vendor ID and device ID, can be PCI_ANY_ID if a driver can handle any vendor or device Id.
__u32 subvendor;
__u32 subdevice;
//Subsystem vendor and subsystem device id
__u32 class;
__u32 class_mask;
//device class such as "parallel", "ehternet", can be PCI_ANY_ID if a driver can handle any type of subsystem ID.
kernel_ulong_t driver_data;
//hold information to differentiate between different devices if it wants to .
};
this macro is used to export pci_device_id
structure to user
space to allow the hotplug and module loading.
static struct pci_driver pci_driver = {
.name = "pci_skel",
.id_table = ids,
.probe = probe,
.remove = remove,
};
static __init pci_skel_init(void)
{
return pci_register_driver(&pci_driver);
}
before a driver can access any device resource(I/O region or
interrupt), the driver must call the pci_enable_device
.
int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(struct pci_dev *dev, int where, u16*val);
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
static unsigned char skel_get_revision(struct pci_dev *dev)
{
u8 revision;
pci_read_config_byte(dev, PCI_REVISION_ID, &revision);
return revision;
}
unsigned long pci_resource_start(struct pci_dev *dev, int bar);
unsigned long pci_resource_end(struct pci_dev *dev, int bar);
unsigned long pci_resource_flags(struct pci_dev *dev, int bar);
Resource flags are:
IORESOURCE_IO
, IORESOURCE_MEM
:
If the associated I/O region exists, one and only one of these
flags is set.
IORESOURCE_PREFETCH
, IORESOURCE_READONLY
:
those flags tell whether a Memory region is prefetchable and/or
write protected. the latter flag is never set for PCI resources.
result = pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &myirq);
if (result) {
/* deal with error */
}