嵌入式Linux驱动开发笔记(未完待续。。。)

news2025/1/18 11:55:11

一、Git仓库用法

1、linu终端输入下面命令安装

git clone https://e.coding.net/weidongshan/linux_course/linux_basic_develop.git

2、
进入到GIT仓库目录

cd  /D/abc/doc_and_source_for_mcu_mpu

在doc_and_source_for_mcu_mpu目录下,执行以下命令获得资料的最新版本。

git pull origin

二、字符设备驱动开发

1、hello驱动程序步骤

  1. 创建 file_operations 结构体(字符设备驱动的核心)
  2. 注册字符设备设备
  3. 写入口函数(相当于main)
  4. 写退出函数

三、字符设备驱动程序源码

#include <linux/mm.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/backing-dev.h>
#include <linux/shmem_fs.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/uio.h>
#include <linux/uaccess.h>


static int major;

static int hello_open (struct inode *node, struct file *filp)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}
static ssize_t hello_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return size;
}    

static ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return size;
}

static int hello_release (struct inode *node, struct file *filp)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

/* 1. create file_operations */
static const struct file_operations hello_drv = {
    .owner      = THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
    .release    = hello_release,
};

/* 2. register_chrdev */

/* 3. entry function */
static int hello_init(void)
{
   major = register_chrdev(0, "100ask_hello", &hello_drv);
   return 0;
}

/* 4. exit function */
static void hello_exit(void)
{
    unregister_chrdev(major, "100ask_hello");
}


module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

在这里插入图片描述
在这里插入图片描述
用户与内核传数据和创建设备文件驱动程序

#include "asm/cacheflush.h"
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/backing-dev.h>
#include <linux/shmem_fs.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/uio.h>

#include <linux/uaccess.h>


static struct class *hello_class;		//定义一个类型,用于创建设备
static int major;						//主设备号
static unsigned char hello_buf[100];	//内核数据缓存

static int hello_open (struct inode *node, struct file *filp)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}
static ssize_t hello_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    unsigned long len = size > 100 ? 100 : size;

    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    copy_to_user(buf, hello_buf, len);		//将内核数据发送给用户

    return len;
}

static ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    unsigned long len = size > 100 ? 100 : size;

    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    copy_from_user(hello_buf, buf, len);

    return len;
}

static int hello_release(struct inode *node, struct file *filp)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

/* 1. create file_operations */
static const struct file_operations hello_drv = {
    .owner      = THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
    .release    = hello_release,
};


/* 2. register_chrdev */

/* 3. entry function */
static int hello_init(void)
{	//申请设备号,只能进行注测主设备号0-255个设备,次设备号全部被占用
    major = register_chrdev(0, "100ask_hello", &hello_drv);  //0代表自动寻找设备号,

	hello_class = class_create(THIS_MODULE, "hello_class");		//创建一个类型
	if (IS_ERR(hello_class)) {
		printk("failed to allocate class\n");
		return PTR_ERR(hello_class);
	}
	//创建一个名字为hello的设备
    device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");  /* /dev/hello */

   return 0;
}


/* 4. exit function */
static void hello_exit(void)
{
    device_destroy(hello_class, MKDEV(major, 0));

    class_destroy(hello_class);

    unregister_chrdev(major, "100ask_hello");
}


module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

应用

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/* 写: ./hello_test /dev/xxx 100ask
 * 读: ./hello_test /dev/xxx
 */
int main(int argc, char **argv)
{
    int fd;
    int len;
    char buf[100];

    if (argc < 2)
    {
        printf("Usage: \n");
        printf("%s <dev> [string]\n", argv[0]);
        return -1;
    }

    // open
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("can not open file %s\n", argv[1]);
        return -1;
    }

    if (argc == 3)
    {
        // write
        len = write(fd, argv[2], strlen(argv[2])+1);
        printf("write ret = %d\n", len);
    }
    else
    {
        // read
        len = read(fd, buf, 100);
        buf[99] = '\0';
        printf("read str : %s\n", buf);
    }
    
    // close
    close(fd);
    return 0;
}

四、 APP使用驱动的4种方式

1、驱动程序:提供能力,不提供策略

  • 非阻塞(查询)

  • 阻塞(休眠-唤醒)

  • poll(定个闹钟)

  • 异步通知
    在这里插入图片描述
    妈妈怎么知道卧室里小孩醒了?

  • 时不时进房间看一下: 查询方式

    • 简单,但是累
  • 进去房间陪小孩一起睡觉,小孩醒了会吵醒她: 休眠-唤醒

  • 不累,但是妈妈干不了活了

  • 妈妈要干很多活,但是可以陪小孩睡一会,定个闹钟: poll 方式

    • 要浪费点时间, 但是可以继续干活。
    • 妈妈要么是被小孩吵醒,要么是被闹钟吵醒。
  • 妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈: 异步通知

    • 妈妈、小孩互不耽误

2、中断的引入

  1. 非阻塞:没有解除睡眠立即返回错误
  2. 阻塞如下图:
    在这里插入图片描述
  3. poll机制如下图
    在这里插入图片描述
  4. 异步通知
    在这里插入图片描述

五、 字符设备的另一种注册方法cdev

驱动程序

#include "asm-generic/errno-base.h"
#include "asm/cacheflush.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/backing-dev.h>
#include <linux/shmem_fs.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/uio.h>
#include <linux/uaccess.h>


static struct class *hello_class;	
static struct cdev hello_cdev;
static dev_t dev;

static unsigned char hello_buf[100];

static int hello_open (struct inode *node, struct file *filp)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}
static ssize_t hello_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    unsigned long len = size > 100 ? 100 : size;

    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    copy_to_user(buf, hello_buf, len);

    return len;
}

static ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    unsigned long len = size > 100 ? 100 : size;

    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    copy_from_user(hello_buf, buf, len);

    return len;
}

static int hello_release (struct inode *node, struct file *filp)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

/* 1. create file_operations */
static const struct file_operations hello_drv = {
    .owner      = THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
    .release    = hello_release,
};


/* 2. register_chrdev */

/* 3. entry function */
static int hello_init(void)
{
    int ret;

    // register_chrdev
	//参数一:分配设备号成功后用来存放分配到的设备号,分配结束后要把主设备号提取出来(major = MAJOR(devno);)因为主设备号会变所以要将变得重新赋值
	//参数二:起始的次设备号,一般为0
	//参数三:count:申请的设备数量,从起始设备号累加。如果在创建一个设备,主设备号跟前一个一样,次设备号为1的设备,依然可以访问,就是同一个设备。
	//参数四:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号
	ret = alloc_chrdev_region(&dev, 0, 2, "hello");		//自动注册设备
	if (ret < 0) 
	{
		printk(KERN_ERR "alloc_chrdev_region() failed for hello\n");
		return -EINVAL;
	}

    cdev_init(&hello_cdev, &hello_drv);	//将hello_drv与hello_cdev链接起来	

    ret = cdev_add(&hello_cdev, dev, 2);//将设备号添加到设备结构体
	if (ret)
    {
		printk(KERN_ERR "cdev_add() failed for hello\n");
		return -EINVAL;
    }
		
	hello_class = class_create(THIS_MODULE, "hello_class");	
	if (IS_ERR(hello_class)) {
		printk("failed to allocate class\n");
		return PTR_ERR(hello_class);
	}
	//创建一名字为hello的设备 
    device_create(hello_class, NULL, dev, NULL, "hello");  /* /dev/hello */

   return 0;
}

/* 4. exit function */
static void hello_exit(void)
{
    device_destroy(hello_class, dev);

    class_destroy(hello_class);

    //unregister_chrdev(major, "100ask_hello");
    cdev_del(&hello_cdev);
    unregister_chrdev_region(dev, 2);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

app

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/* 写: ./hello_test /dev/xxx 100ask
 * 读: ./hello_test /dev/xxx
 */
int main(int argc, char **argv)
{
    int fd;
    int len;
    char buf[100];

    if (argc < 2)
    {
        printf("Usage: \n");
        printf("%s <dev> [string]\n", argv[0]);
        return -1;
    }
    // open
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("can not open file %s\n", argv[1]);
        return -1;
    }
    if (argc == 3)
    {
        // write
        len = write(fd, argv[2], strlen(argv[2])+1);
        printf("write ret = %d\n", len);
    }
    else
    {
        // read
        len = read(fd, buf, 100);
        buf[99] = '\0';
        printf("read str : %s\n", buf);
    }
    // close
    close(fd);
    return 0;
}

六、 通用框架1

1、GPIO子系统
在开发板上执行如下命令查看已经在使用的GPIO状态:

# cat /sys/kernel/debug/gpio
gpiochip0: GPIOs 0-15, parent: platform/soc:pin-controller@50002000, GPIOA:
 gpio-10  (                    |heartbeat           ) out lo
 gpio-14  (                    |shutdown            ) out hi

gpiochip1: GPIOs 16-31, parent: platform/soc:pin-controller@50002000, GPIOB:
 gpio-26  (                    |reset               ) out hi ACTIVE LOW

gpiochip2: GPIOs 32-47, parent: platform/soc:pin-controller@50002000, GPIOC:

gpiochip3: GPIOs 48-63, parent: platform/soc:pin-controller@50002000, GPIOD:

怎么确定GPIO引脚的编号?方法如下:

① 先在开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录:(这个后面的数是起始地址,不跟上面那个查到的组数一样)
在这里插入图片描述
② 然后进入某个gpiochipXXX目录,查看文件label的内容,就可以知道起始号码XXX对于哪组GPIO
那么GPIO4_14的号码是96+14=110,可以如下操作读取按键值:

[root@100ask:~]# echo 110 > /sys/class/gpio/export              // gpio_request
[root@100ask:~]# echo in > /sys/class/gpio/gpio110/direction    // gpio_direction_input
[root@100ask:~]# cat /sys/class/gpio/gpio110/value              // gpio_get_value
[root@100ask:~]# echo 110 > /sys/class/gpio/unexport            // gpio_free

在这里插入图片描述

GPIO子系统函数有新、老两套:

descriptor-basedlegacy
获得GPIO
gpiod_getgpio_request
gpiod_get_index
gpiod_get_arraygpio_request_array
devm_gpiod_get
devm_gpiod_get_index
devm_gpiod_get_array
设置方向
gpiod_direction_inputgpio_direction_input
gpiod_direction_outputgpio_direction_output
读值、写值
gpiod_get_valuegpio_get_value
gpiod_set_valuegpio_set_value
释放GPIO
gpio_freegpio_free
gpiod_putgpio_free_array
gpiod_put_array
devm_gpiod_put
devm_gpiod_put_array

2、中断函数

2.1 使用中断的流程

在驱动程序里使用中断的流程如下:

  • 确定中断号

  • 注册中断处理函数,函数原型如下:

  int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);
  • 在中断处理函数里

    • 分辨中断
    • 处理中断
    • 清除中断

2.2 函数细节

request_irq函数的第1个参数是中断号,可以根据GPIO函数获得中断号:

int gpio_to_irq(unsigned int gpio);
int gpiod_to_irq(const struct gpio_desc *desc);

request_irq函数的第2个参数是函数指针:

enum irqreturn {
	IRQ_NONE		= (0 << 0),
	IRQ_HANDLED		= (1 << 0),
	IRQ_WAKE_THREAD		= (1 << 1),
};
typedef enum irqreturn irqreturn_t;
typedef irqreturn_t (*irq_handler_t)(int irq, void *dev);

request_irq函数的第3个参数有如下取值:
(上升沿触发、下降沿触发…)

#define IRQF_TRIGGER_NONE	0x00000000
#define IRQF_TRIGGER_RISING	0x00000001
#define IRQF_TRIGGER_FALLING	0x00000002
#define IRQF_TRIGGER_HIGH	0x00000004
#define IRQF_TRIGGER_LOW	0x00000008

#define IRQF_SHARED		0x00000080

request_irq函数的第4个参数是中断的名字,可以在执行cat /proc/interrupts的结果里查看。

request_irq函数的第5个参数是给中断处理函数使用的。
在这里插入图片描述
2.3 代码

driver

#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

struct gpio_desc{							//定义一个中断结构体
	int gpio;
	int irq;
    char *name;
    int key;
	struct timer_list key_timer;
} ;

static struct gpio_desc gpios[2] = {   		//中断结构体赋初值
    {131, 0, "gpio_100ask_1", 1,},
    {132, 0, "gpio_100ask_2", 2,},  
};

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_class;

/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

struct fasync_struct *button_fasync;

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
	return (r == w);
}

static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}

static void put_key(int key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static int get_key(void)
{
	int key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}

static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);

// static void key_timer_expire(struct timer_list *t)
static void key_timer_expire(unsigned long data)
{
	/* data ==> gpio */
	// struct gpio_desc *gpio_desc = from_timer(gpio_desc, t, key_timer);
	struct gpio_desc *gpio_desc = (struct gpio_desc *)data;
	int val;
	int key;

	val = gpio_get_value(gpio_desc->gpio);		//读取引脚  0或1

	//printk("key_timer_expire key %d %d\n", gpio_desc->gpio, val);
	key = (gpio_desc->key) | (val<<8);  //key值用来放在环形数组中,用key第八位看按键是否按下
	put_key(key);//放入环形数组
	wake_up_interruptible(&gpio_wait);
	kill_fasync(&button_fasync, SIGIO, POLL_IN);
}

/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	int key;

	if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
		return -EAGAIN;
	
	wait_event_interruptible(gpio_wait,!is_key_buf_empty());
	key = get_key();
	err = copy_to_user(buf, &key, 4);
	
	return 4;
}

static ssize_t gpio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    unsigned char ker_buf[2];
    int err;

    if (size != 2)
        return -EINVAL;

    err = copy_from_user(ker_buf, buf, size);
    
    if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))
        return -EINVAL;

    gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);
    return 2;    
}

static unsigned int gpio_drv_poll(struct file *fp, poll_table * wait)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_wait, wait);
	return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

static int gpio_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
		return 0;
	else
		return -EIO;
}

/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_drv_read,
	.write   = gpio_drv_write,
	.poll    = gpio_drv_poll,
	.fasync  = gpio_drv_fasync,
};

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_desc *gpio_desc = dev_id;
	printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);
	//jiffies是全局变量  
	mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);		//消抖,每次抖动时长小于(HZ/5)*Tms,都会推迟进入中断服务程序
	return IRQ_HANDLED;
}

/* 在入口函数 */
static int __init gpio_drv_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	for (i = 0; i < count; i++)
	{		
		gpios[i].irq  = gpio_to_irq(gpios[i].gpio);		//调用函数获得中断号
		//定时器初始化函数 参数一:定时器时间 参数二:中断服务程序 参数三:传入中断服务程序的参数
		setup_timer(&gpios[i].key_timer, key_timer_expire, (unsigned long)&gpios[i]);
	 	//timer_setup(&gpios[i].key_timer, key_timer_expire, 0);
		gpios[i].key_timer.expires = ~0;
		add_timer(&gpios[i].key_timer);
		err = request_irq(gpios[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpios[i]);  //注册中断
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_gpio_key", &gpio_key_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "100ask_gpio_key_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_gpio_key");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "100ask_gpio"); /* /dev/100ask_gpio */
	
	return err;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
static void __exit gpio_drv_exit(void)
{
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "100ask_gpio_key");

	for (i = 0; i < count; i++)
	{
		free_irq(gpios[i].irq, &gpios[i]);
		del_timer(&gpios[i].key_timer);
	}
}

/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(gpio_drv_init);
module_exit(gpio_drv_exit);

MODULE_LICENSE("GPL");

app

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>

static int fd;

/*
 * ./button_test /dev/100ask_button0
 *
 */
int main(int argc, char **argv)
{
	int val;
	struct pollfd fds[1];
	int timeout_ms = 5000;
	int ret;
	int	flags;

	int i;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR | O_NONBLOCK);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	for (i = 0; i < 10; i++) 
	{
		if (read(fd, &val, 4) == 4)
			printf("get button: 0x%x\n", val);
		else
			printf("get button: -1\n");
	}

	flags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);

	while(1)
	{
		if (read(fd, &val, 4) == 4)
			printf("get button: 0x%x\n", val);
		else
			printf("while get button: -1\n");
	}
	close(fd);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/41433.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【1752. 检查数组是否经排序和轮转得到】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个数组 nums 。nums 的源数组中&#xff0c;所有元素与 nums 相同&#xff0c;但按非递减顺序排列。 如果 nums 能够由源数组轮转若干位置&#xff08;包括 0 个位置&#xff09;得到&#xf…

appnium环境搭建

一、安装JDK 官网下载对应版本直接安装 二、安装Android SDK 官网下载对应版本直接安装 https://www.androiddevtools.cn/ 三、安装安卓模拟器 我使用的是夜神模拟器&#xff0c;官网下载直接安装 夜神安卓模拟器-安卓模拟器电脑版下载_安卓手游模拟器_手机模拟器_官网 …

springboot整合SpringSecurity并实现简单权限控制

目录 一、SpringSecurity介绍 案例效果&#xff1a; 二、环境准备 2.1 数据库 2.2 项目准备 三、确保项目没问题后开始使用 3.1、Security的过滤链&#xff1a; 3.2、自定义用户名密码登录&#xff1a; 方式1&#xff1a;将用户名密码写在配置文件里 方式2&#xff1a;使…

刷题之莲子的软件工程学和机械动力学以及物理热力学

目录 1、莲子的软件工程学 1&#xff09;题目 2&#xff09;题目解析 3&#xff09;代码 2、莲子的机械动力学 2&#xff09;题目解析 3&#xff09;代码 3、莲子的物理热力学 1&#xff09;、题目 2&#xff09;题目解析 1、莲子的软件工程学 1&#xff09;题目 题目背景…

Linux下的进程控制-进程程序替换

这篇主要说一下Linux下的进程控制中最后一部分内容&#xff1a;进程程序替换。 文章目录1. 进程程序替换1.1 为什么要进程程序替换1.2 替换原理1.3 如何进行程序替换1.3.1 execl函数1.3.2 引入子进程的程序替换1.3.3 execv函数1.3.4 execlp函数和execvp函数1.3.5 如何执行其它…

Flutter自定义对话框返回相关问题汇总

Flutter自定义对话框&#xff0c;禁用系统返回按钮 - WillPopScope 使用WillPopScope即可&#xff0c;重点onWillPop方法: Future<bool> _onWillPop()>new Future.value(false); 由于要弹出dialog&#xff0c;我这里是禁掉返回按钮&#xff0c;当然也可以在这里做一下…

基于SpringBoot的二手商品交易平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SpringBoot 前端&#xff1a;Vue、HTML 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#…

3.6.4、随机接入-CSMA/CA协议

无线局域网使用的协议 1、基本概念 对于上述无线局域网为什么 A 和 C 都检测不到对方的无线信号 因为 C 不在 A 的范围内&#xff0c;C 发送无线信号会导致 A 检测不到 C 在发送 对于上述使用广播信道的有线局域网就不会存在这样的问题 总线上某个主机发送的信号&#xff0…

JVM虚拟机字节码执行引擎——类文件和类加载之前必看

文章目录虚拟机字节码执行引擎运行时栈帧结构局部变量表&#xff08;Local Variables&#xff09;操作数栈动态链接&#xff08;Dynamic Linking&#xff09;方法返回地址附加信息方法调用解析分派虚方法和非虚方法普通调用指令&#xff1a;动态调用指令&#xff1a;动态类型语…

SpringBoot SpringBoot 原理篇 2 自定义starter 2.7 开启yml 提示功能

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 原理篇 文章目录SpringBootSpringBoot 原理篇2 自定义starter2.7 开启yml 提示功能2.7.1 问题引入2.7.2 开启yml提示功能2.…

【矩阵论】正规方程——求解

5.2 正规方程 AHAxAHb为Axb的正规方程\begin{aligned} A^HAxA^Hb为Axb的正规方程 \end{aligned} AHAxAHb为Axb的正规方程​ 5.2.1 正规方程必有解 正规方程 AHAxAHbA^HAxA^HbAHAxAHb 必有解 &#xff0c;且特解为 x0Abx_0A^bx0​Ab &#xff0c;使 AHAx0AHbA^HAx_0A^HbAHAx0​…

一文熟悉 Go 的循环结构 —— for 循环

哈喽大家好&#xff0c;我是陈明勇&#xff0c;今天分享的知识是 Go 的循环结构。如果本文对你有帮助&#xff0c;不妨点个赞&#xff0c;如果你是 Go 语言初学者&#xff0c;不妨点个关注&#xff0c;一起成长一起进步&#xff0c;如果本文有错误的地方&#xff0c;欢迎指出&a…

【Spring】——10、@PostConstruct注解和@PreDestroy注解

&#x1f4eb;作者简介&#xff1a;zhz小白 公众号&#xff1a;小白的Java进阶之路 专业技能&#xff1a; 1、Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理 2、熟悉Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理&#xff0c;具备⼀定的线…

OpenStack集群部署——Keystone部署(二)

三、Keyston-认证服务 3.1 Keyston介绍 Keyston介绍 补充 3.2 安装时间同步器 ----------------------------------------------------使用chrony安装------------------------------------------------- ####所有节点 #下载安装chrony yum -y install chrony #修改配置…

【心电信号】Simulink胎儿心电信号提取【含Matlab源码 1550期】

⛄一、心电信号简介 0 引言 心电信号是人类最早研究的生物信号之一, 相比其他生物信号更易于检测, 且具有直观的规律。心电图的准确分析对心脏病的及早治疗有重大的意义。人体是一个复杂精密的系统, 有许多不可抗的外界因素, 得到纯净的心电信号非常困难。可以采用神经网络算法…

MongoDB 分片集群

之前说到了主从集群&#xff0c;关于主从集群的搭建以及细节后面会再次分享&#xff0c;这次我们先初步来看看 分片集群 举个例子 例如我们有几百G甚至更多的数据&#xff0c;可是我们只有单个副本集&#xff0c;数据量这么大&#xff0c;网络 IO &#xff0c;CPU &#xff0c…

《深度学习的数学》chap1 神经网络的思想

《深度学习的数学》chap1 神经网络的思想 文章目录1-1 神经网络和深度学习神经网络用神经网络实现的人工智能“人教导机器”类型的人工智能的问题1-2 神经元工作的数学表示整理神经元的工作神经元工作的数学表示点火条件的图形表示1-3 激活函数&#xff1a;将神经元的工作一般化…

开源项目-排班管理系统,考勤管理系统

哈喽&#xff0c;大家好&#xff0c;今天给大家带来一个开源系统-排版管理系统 ​​​​​​​git上搜索可以FinalScheduler-master可以了解详情 也可以通过csdn下载​​​​​​​ 该系统主要用于人员的排班使用&#xff0c;主要用人员管理&#xff0c;排班管理&#xff0c…

Java-ForkJoinPool(线程池-工作窃取算法)

文章目录概述工作窃取算法工作窃取算法的优缺点使用 ForkJoinPool 进行分叉和合并ForkJoinPool使用RecursiveActionRecursiveTaskFork/Join 案例Demo概述 Fork 就是把一个大任务切分为若干个子任务并行地执行&#xff0c;Join 就是合并这些子任务的执行结果&#xff0c;最后得到…

《精神与爱欲》爱源于母性,且超越性别

《精神与爱欲》爱源于母性&#xff0c;且超越性别 赫尔曼黑塞&#xff08;1877-1962&#xff09;&#xff0c;作家&#xff0c;诗人&#xff0c;画家。1877年生于德国&#xff0c;1924年入籍瑞士。1946年获诺贝尔文学奖。被誉为“德国浪漫派的最后一位骑士”。 文章目录《精神与…