韦东山 嵌入式Linux应用开发基础知识 上【gcc makefile 输入设备

1 main的输入参数,并且在命令行运行文件的时候输入

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

我的练习

先写了个单纯输出的hello
在这里插入图片描述
按照教程里那样写hello
在这里插入图片描述
所以gcc编译过程应该是

  1. 先创建一个.c文件
  2. gcc -c -o hello.o hello.c 在当前文件夹生成一个.o文件
  3. gcc -o hello hello.o 在当前文件夹生成一个可执行文件
  4. ./hello 执行文件,后面可以带输入参数,用空格区分。而且这个文件名本身算一个参数,放在argv[0]

2 gcc编译器

Linux里不是用keil也不是用vs 而是用gcc 在命令行里编译文件。

在这里插入图片描述

在这里插入图片描述
这是第三课第2集的图【这是第三课第2集的图

在这里插入图片描述


预处理就是把宏定义替换进代码,把头文件读入。
汇编的时候是把代码转换为汇编程序,这时候如果程序有误会报错。

在这里插入图片描述

在这里插入图片描述
上图是同时把两个文件作为同一个项目进行编译,生成一个test工程文件

但是如果要修改main.c的话,再执行这条命令,sub也不得不重新编译一次。

在这里插入图片描述
按上图先生成.o文件【机器码】后再链接
sub就不必费时间重新编译,但第三句那里还是要写一遍进行链接。

如果文件太多,不知道改了哪些.c文件

看文件时间,如果.o文件比.c文件时间早,就得重新生成.o文件,然后再重新链接生成test文件

在这里插入图片描述

./test
就能运行了


在这里插入图片描述
末尾加一个-v能看到系统会去哪些路径找头文件里的代码文件
在这里插入图片描述


下图是作者把自己的头文件用了尖括号
然后用-I命令把当前的文件夹路径放进了尖括号头文件的搜索路径的列表里【看这效果是一次性的,如果下一次gcc编译不加-I命令的话,程序仍然不会去找 ./ 这个路径

在这里插入图片描述


在这里插入图片描述

这句是把sub这个子函数的文件压缩成静态库
然后再跟main链接到一起

在这里插入图片描述
.so是动态库,动态库的文件大小会比静态库小一些

动态库文件要指定读取路径,不然会显示找不到文件
在这里插入图片描述

3 makefile

说是不用学得太仔细,但如果非要仔细学makefile的话看这里

【注意,在makefile里的tab键是重要格式,tab键后必须跟命令,如果光有tab键就换行了 程序会出错,如果命令前是空格而不是tab则无法识别到命令】

makefile跟.c .h一样是一个某种格式的代码文件
它看起来是帮忙执行gcc编译的批处理文件?
在编写的时候有固定语法
在这里插入图片描述
在这里插入图片描述

如果a.o 或b.o更新了,就执行gcc链接生成test

如果a.c更新了,就执行 gcc -c 重新编译生成a.o文件
b.o也是

在这里插入图片描述
如果用vi改了a.c 文件,make文件就会自动gcc -c一下a文件,报了个警告 没事,然后重新gcc一下test文件

通配符

为了不要a.c b.c c.c 挨个写,所以用各种符号减少重复
在这里插入图片描述

下图是替换后的样子
在这里插入图片描述

加了一个c.c文件放了一个新的子函数。对比一下差别。
在这里插入图片描述

指令

在这里插入图片描述
多加了一个指令,输入clean的时候就执行那句rm操作
.PHONY:是为了声明clean 跟test那个不一样 并不是一个文件。
在这里插入图片描述

变量

在这里插入图片描述
【加@是为了在运行时不显示echo这个指令本身
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
怪了。对延时变量来说,C无论是在前文赋值还是在后文赋值都是先于命令的。
而对即时变量却直接就是赋值时还未定义的为空值的C。

make时设定变量数值

在这里插入图片描述
在这里插入图片描述
嗯,输入的定义是优先于?=的赋值的,这个好理解。

函数

在这里插入图片描述

foreach

foreach是 把list里的所有变量用var取出来 然后挨个执行test这句内容

在这里插入图片描述

make显示 B = a.o b.o c.o

filter

在这里插入图片描述
%是通配符,在这里代替字母
在这里插入图片描述

wildcard

pattern的位置是我们打算找的一些文件。在make时程序会在当前目录里找有没有符合要求的文件
在这里插入图片描述
输出是 files3 = a.c b.c c.c 【因为当前路径里只有这3个文件符合要求

patsubst

在这里插入图片描述

在这里插入图片描述

综合运用

阿巴阿巴。。。
编程没天赋的人可能就是这种感觉了?
我当时学c的时候明明理解得挺快的啊。。。。
Linux Makefile 生成 *.d 依赖文件以及 gcc -M -MF -MP 等相关选项说明

4 文件io

【这视频内容有点离题啊
写代码的时候
用man指令可以查函数的用法【如printf和read】,man有好几本手册可以查函数,不同手册里可能有同名函数,可以指定手册【printf在3号手册
$ man 3 printf】 可以查函数的头文件和输入参数,在man里 f和b是翻页,q是退出。

在这里插入图片描述
说是代码在调用open或者read的时候,用户区会向内核区抛出异常,并且这个异常附带着参数 以指向函数所对应的操作

在这里插入图片描述

7 输入系统应用编程

01_输入系统框架及调试

在这里插入图片描述
核心层负责把收到的各类数据进行分类统一处理
在这里插入图片描述
在这里插入图片描述


每个设备输入的数据类型不一样多,所以都要发一个同步信号表示自己当前发完了一批数据。

在这里插入图片描述
上图是触摸屏传来的数据。方框内的是同步信号。。【。我看着是按时间来分的啊?跟数据的完整性有关吗??】

这句指令可以知道每个设备节点的信息

在这里插入图片描述

I : 总线 厂家 产品 版本号
N : char *name
P : 物理上的名字,这里写的是触摸屏 char *phys
H :这个设备是event0
B : EV对应的是下图代码里的evbit 支持的事件event的置位情况。数值是b代表的是二进制1011 有3个数据位置1,也就是0号,1号,3号数据需要传输,那就是上上上图宏定义里对Event type里列出来的EV_SYN ,EV_KEY和EV_ABS这三个数据。
B : ABS是绝对位移相关信息code的置位情况
在这里插入图片描述
在这里插入图片描述
这里的信息和内核里的信息是一一对应的
在这里插入图片描述

下图的code部分就是对应的宏定义的编号【所以为什么会有39啊。上面说置位了35 36 没说39啊
type为1代表的是按键类事件,code代表的是第014a号键 value代表具体的数值是0001 表示按下
在这里插入图片描述

我的练习

看一下现在系统上有哪些设备,我看到了触摸屏和我的蓝牙鼠标

cat /proc/bus/input/devices
在这里插入图片描述

在这里插入图片描述
看一下我的鼠标都发些什么信息,系统提示我权限不够,所以我加了sudo 【为后文的失败埋下伏笔

sudo hexdump /dev/input/event5

木头  15:03:48
00263f0 之前都是移动,然后我按下并松开左键进行截屏,不过不知道为什么没有全零同步帧,之前移动并停下的时候也时常没有同步帧出现在最后。
感觉现在的格式应该跟教程里的不一样了,反正【微秒】那栏肯定不对。

02_现场编程读取获取输入设备信息

写程序读取内核里的设备数据
在这里插入图片描述
目标是:只要运行01_get…这个可执行文件,并输入设备路径 就能读取到对应信息。

在这里插入图片描述

在这段内核代码里cmd有EVIOCGID这个分类,这里有设备id这个信息被保存在input_id这个结构体里
这个结构体具体有下列信息
在这里插入图片描述

我们的程序通过一个包装好的函数去获取信息,只需要设备路径和所需内容的对应置位信息在这里插入图片描述
因此,我们只需要新建一个struct input_id thisId;
写一句
fd=open(…)
err = ioctl(fd,EVIOCGID,&thisId);
就可以把内核里的EVIOCGID这个case里的信息存到thisId结构体变量里去。

但是并不是所有信息都能简单靠EVIOCGID这样一个宏就能去找到的,而是需要一个帮忙转换格式用的函数,EVIOCGBIT()函数。
下图就是对应置位信息所代表的含义,而我们不需要去记,用函数就好了。【韦东山说 具体什么时候用宏定义,什么时候用EVIOCGBIT()函数,得具体看内核代码来决定】
在这里插入图片描述
下图就是放在request这个位置的函数
在这里插入图片描述
【把0改成1 就能得到keybit的信息
在这里插入图片描述

我们写的读取信息的代码长这样

unsigned int evbit[2] 是因为内核里的evbit是 unsigned long
在这里插入图片描述
那个头文件,我们包含的不是内核的input头文件,而是交叉编译的工具链里的头文件。
在这里插入图片描述
不是很明白他是怎么找的,但总之是包含
在这里插入图片描述
然后打开文件 open()函数可以用 $ man 2 open 查看用法
1 看所需头文件
2 输入参数格式
在这里插入图片描述

为了显示内核里的event的这些对应的宏信息。在这里插入图片描述
代码里写了一个字符串数组保存了宏信息,并在读取了设备数据之后对应显示。【我总是想把这些宏的16进制数换成2进制数啊。但这里的用法真的不是这样。这里的宏的数值是代表设备里数据的第几bit。
在这里插入图片描述
evbit 是uint类型的数组,sizeof(evbit )为8字节 。总共有len个数据,用uchar 类型的byte拆成8份挨个取出,每个数据有8bit,如果数据中的某个位为1,则显示对应那一组的对应那个位的ev数据.
所以,event types那些宏就是第x个位置1使能的意思咯?

我的练习
  1. 设备权限
    在这里插入图片描述

第二次的打开失败估计就是因为权限问题。
但是我甚至找不到我的蓝牙鼠标的文件位置,改不了权限【顺便一提,我打开can通信的那个代码也不算输入设备,那个在Linux看来应该是SPI通信】

直接修改设备文件权限没有意义,设备拔插时,udev 会更新 /dev,并且每次设备插入的时候,Bus 和 Device 的值都不确定。
如何修改 Linux 设备访问权限

笑死,我没改权限 我直接 变身root

$ su root

  1. 头文件
    在这里插入图片描述
    所需头文件就是韦东山从Linux内核里找的头文件 <linux/input.h>

  2. OK了。【我确实需要makefile文件。编译麻烦死了 】
    在这里插入图片描述

03_四种方式读取输入数据

有4种方式读取设备信息在这里插入图片描述

在这里插入图片描述


不等和死等模式 【open()输入参数里的nonblock标志位

在这里插入图片描述

在这里插入图片描述
上述两个方法就是open函数的一个输入参数的区别罢了。

那么,在运行代码文件的时候,除了输入必须的<设备路径>之外,还可选输入noblock这个输入
在这里插入图片描述

在代码里用strcmp函数将输入参数进行对比,然后执行对应的open方式

在这里插入图片描述
【淦,我没注意到strcmp前面还有个取反! 这个字符串比较的函数是在字符串相等时返回0】
fd指针是当时open()函数打开的event0这个设备,在这里是触摸屏
event这个结构体指针是从内核代码里知道的。我们调用read的时候,系统会对应唤醒evdev_read()这个函数,系统会把设备数据塞进__user *buffer里,函数内部代码会具体用到input_event这个结构体类型【但我看不懂,所以就不截图了。】总之,知道了数据保存的形式之后就能按照sizeof来读取数据。
如果用了O_NONBLOCK这个标志,那在程序运行的时候就会不断输出else里的语句。
如果没用那个标志,就会一直等着触摸屏输入【就是卡在了read()这函数里
在这里插入图片描述
在这里插入图片描述

我的练习

确认了阻塞方式就是卡在了read()函数内部。明明是读取函数进行阻塞,但标志位却是在open函数里设置。看内核代码,是检查fd句柄里的f_flags的对应bit的标志位决定是不是直接return的。
在这里插入图片描述


等一会模式

在这里插入图片描述

这个方法的做法是 在read函数之前加一句poll函数。
poll函数可以同时监视好几个文件的IO情况,所以第一个参数是一个结构体指针数组。结构体的组成看下图的底部。
本例只监视一个文件,所以只创建struct pollfd fds[1]。这个结构体需要在传入函数之前赋值。同时这个结构体里除了设备的fd之外【诶,才发现fd是int,怎么不是指针,难道是索引节点的inode吗】还需要设置事件events,具体选择可以看上图的表格,我们选用的是有数据可读的情况。第三个结构体是函数自己写的,内容也是表格里的那些事件类型。events是我们期望去了解的事件,revents是当函数做完事情之后,它会记录一下自己刚才完成的是哪类事件。
为什么不把revents搞成返回值呢?因为返回值要返回的是有多少个文件IO有事了。在我们这个例子里,返回值最多只能是1,如果是0就是没消息而且等待超时了,返回-1就是出错了。
话说回来,poll还有第二个参数,是对应的文件数量
第三个参数,限时时间,毫秒。本文设置3000ms

那么,在程序运行时,poll函数如果没读到内容,会等3秒,这时候是堵在poll这里的,中间有信息的话随时能放行给read,如果等3秒还没数据,就会返回超时,然后让程序跑一圈,重新回到poll的时候再等3秒。

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

在这里插入图片描述

在这里插入图片描述

select和epoll的区别

select的时间复杂度O(n)。它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。 epoll的时间复杂度O(1)。epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))
poll函数详解
linux select函数详解

我的练习

要注意输入数据类型,我差点把poll的第一个参数直接用了之前的fd句柄。。但是第二个参数直接写1能用【当然最好还是憋那么干】
另外,我加了个计时的函数,基本上不太准,而且鼠标在动的时候计时数据经常错,尽量不要用。
在这里插入图片描述


有事喊我模式

在这里插入图片描述
像qt那样注册一个回调函数。只需要信号类型和对应的函数 这俩绑定到一块去。这样只要来了信号就知道该干啥了。
然后,那个读取设备的进程有一个进程ID,叫PID。进程是内核系统里的事,要获取到这个PID之后发给设备驱动,告诉驱动:如果你有数据来了,发个信号,我让这个进程给你办事。
主函数的while循环里没有读取设备内容的函数。但是事先要记得打开信号接收的标志位。
然后就可以打开设备了open()

  1. 注册信号函数
    sighandler_t 是函数类型。
    这个函数类型输入一个int 无返回值

在这里插入图片描述
在这里插入图片描述
这句写在main函数里
在这里插入图片描述
这个是子函数
在这里插入图片描述

  1. 打开驱动程序
    这一步和以前一样的
    在这里插入图片描述
  2. 告知进程号
    教程没说中间的宏是干啥用的。。
    在这里插入图片描述
  3. 设置flag
    这里也没细讲
    在这里插入图片描述
    但是搞完这些就完成了,后面可以写自己的while(1)里的随便什么任务了,设备如果有消息就会通过回调那个注册的信号处理函数进行处理。

总结一下全程:

在这里插入图片描述

Linux进程间通信详解(六) 信号种类及函数

我的练习

man signal 里没有提到SIGIO这个宏,但是编辑器自动补全里面有,纳闷。
教程里用while,但是我寻思着 只要有IO信号就会进回调函数,应该用if也一样。
有一些宏跟教程对不上。
没有进入回调函数,开始找bug
看看fcntl()的资料
   fcntl()针对(文件)描述符提供控制.
   参数fd是被参数cmd操作(如下面的描述)的描述符.
   针对cmd的值,fcntl能够接受第三个参数(arg)
fcntl函数有5种功能:
1.复制一个现有的描述符(cmd=F_DUPFD).
2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

O_ASYNC 当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候

fcntl的返回值: 与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD,F_GETFD,F_GETFL以及F_GETOWN。第一个返回新的文件描述符,第二个返回相应标志,最后一个返回一个正的进程ID或负的进程组ID。

Linux fcntl函数详解

//能成功运行的宏
	signal(SIGIO, sig_func);
	fcntl(fd,__F_SETOWN,getpid());
	flags = fcntl(fd , F_GETFL);
	fcntl(fd,F_SETFL,flags|O_ASYNC);

话说,进了信号回调函数之后再回到while循环,sleep(2)就不管用了。【是睡不着了么?/狗头】

我的练习代码

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本程序的Makefile分为3类: 1. 顶层目录的Makefile 2. 顶层目录的Makefile.build 3. 各级子目录的Makefile 一、各级子目录的Makefile: 它最简单,形如下: EXTRA_CFLAGS := CFLAGS_file.o := obj-y += file.o obj-y += subdir/ "obj-y += file.o" 表示把当前目录下的file.c编进程序里, "obj-y += subdir/" 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。 "EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置 "CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置 注意: 1. "subdir/"中的斜杠"/"不可省略 2. 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用 3. CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者组成xxx.c的编译选项 二、顶层目录的Makefile: 它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外, 主要是定义工具链前缀CROSS_COMPILE, 定义编译参数CFLAGS, 定义链接参数LDFLAGS, 这些参数就是文件中用export导出的各变量。 三、顶层目录的Makefile.build: 这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o 详细的讲解请看视频。 四、怎么使用这套Makefile: 1.把顶层Makefile, Makefile.build放入程序的顶层目录 在各自子目录创建一个空白的Makefile 2.确定编译哪些源文件 修改顶层目录和各自子目录Makefile的obj-y : obj-y += xxx.o obj-y += yyy/ 这表示要编译当前目录下的xxx.c, 要编译当前目录下的yyy子目录 3. 确定编译选项、链接选项 修改顶层目录Makefile的CFLAGS,这是编译所有.c文件时都要用的编译选项; 修改顶层目录Makefile的LDFLAGS,这是链接最后的应用程序时的链接选项; 修改各自子目录下的Makefile: "EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置 "CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置 4. 使用哪个编译器? 修改顶层目录Makefile的CROSS_COMPILE, 用来指定工具链的前缀(比如arm-linux-) 5. 确定应用程序的名字: 修改顶层目录Makefile的TARGET, 这是用来指定编译出来的程序的名字 6. 执行"make"来编译,执行"make clean"来清除,执行"make distclean"来彻底清除

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值