https://www.bilibili.com/video/BV1kk4y117Tu
4【第四章】文件I/O
4_6_综合实验_处理表格
hexdump -C main.c
可以用来查看二进制数据,并且在右侧显示相应的文本对照。
4_7_文件IO系统调用内部机制
/proc/pid/fd
对应进程打开的 fd,默认有 0,1,2 三个,对应 stdin, stdout, stderr.
内部调用
fs/open.c
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
->do_sys_open(AT_FDCWD, filename, flags, mode)
do_sys_open(AT_FDCWD, filename, flags, mode)
->fd = get_unused_fd_flags(flags)
获取未使用的文件描述符,struct file *f = do_filp_open(dfd, tmp, &op)
打开文件结构体 ,fd_install(fd, f)
把文件结构体放入文件描述符数组中。fs/namei.c
struct file *do_filp_open(int dfd, struct filename *pathname, const struct open_flags *op)
fs/file.c
int get_unused_fd_flags(unsigned flags)
->__alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags)
->fdt = files_fdtable(files)
->fd = find_next_fd(fdt, fd)
,files->next_fd = fd + 1
fs/file.c
void fd_install(unsigned int fd, struct file *file)
->__fd_install(current->files, fd, file)
->fdt = rcu_dereference_sched(files->fdt)
,rcu_assign_pointer(fdt->fd[fd], file)
current
来源于include/asm-generic
#define current get_current()
->#define get_current() (current_thread_info()->task)
->thread_info.h
里面的struct thread_info
->sched.h
struct task_struct *task
->fdtable.h
struct files_struct *files
->struct fdtable fdtab
4_8_dup函数的使用
int dup(int old);
, 返回一个指向 old 的新的最小未使用 fd.int dup2(int old,int new);
, 先检查 new 有没有被打开,如果打开,就先关闭。 然后让 new 指向 old.
第7章 输入系统应用编程
7.2 现场编程获取输入设备信息
drivers\input\evdev.c
,static long evdev_do_ioctl(struct file *file, unsigned int cmd, void __user *p, int compat_mode)
内核中 ioctl 对应的函数evdev_do_ioctl
中 switch 判断分支EVIOCGVERSION
在include\uapi\linux\input.h
中#define EVIOCGVERSION _IOR('E', 0x01, int) /* get driver version */ #define EVIOCGID _IOR('E', 0x02, struct input_id) /* get device ID */ #define EVIOCGREP _IOR('E', 0x03, unsigned int[2]) /* get repeat settings */ #define EVIOCSREP _IOW('E', 0x03, unsigned int[2]) /* set repeat settings */
define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len) / get event bits /
+ 获取设备 id 代码
struct input_id id;
err = ioctl(fd, EVIOCGID, &id);
if (err == 0)
{
printf("bustype = 0x%x\n", id.bustype );
printf("vendor = 0x%x\n", id.vendor );
printf("product = 0x%x\n", id.product );
printf("version = 0x%x\n", id.version );
}
+ 获取设备 evbits 代码, `EVIOCGBIT(ev,len)` 中 ev 为 0 表示获取 evbit, 为 1 表示获取 keybit,按照 `include\linux\input.h` 里面的 `struct input_dev` 中的偏移来算。
unsigned int evbit[2];
len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
if (len > 0 && len <= sizeof(evbit))
{
printf("support ev type: ");
for (i = 0; i < len; i++)
{
byte = ((unsigned char *)evbit)[i];
for (bit = 0; bit < 8; bit++)
{
if (byte & (1<<bit)) {
printf("%s ", ev_names[i*8 + bit]);
}
}
}
printf("\n");
}
## 7_3 查询 休眠唤醒 读取输入数据
+ read 对应的驱动中的函数在 `drivers\input\evdev.c` `static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)`, 通过 `input_event_to_user(buffer + read, &event)` 给用户空间传数据,`struct input_event event`.
+ 应用中读取数据的代码:
while (1)
{
len = read(fd, &event, sizeof(event));
if (len == sizeof(event))
{
printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
}
else
{
printf("read err %d\n", len);
}
}
## 7_6 电阻屏和电容屏
+ 上报数据中的 `ABS_MT_TOUCH_MAJOR` 和 `ABS_MT_WIDTH_MAJOR` 指的是接触区域的椭圆尺寸,正常没啥用。
## 7_7 tslib框架分析
** tslib,暂时不太需要,以后需要的时候,再回头看。**
# 第8章 网络通信
## 8_2 tcp 编程
+ `tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */` , 自己给出来的数据,需要进行字节序转换。
+ `iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));` 把方便的 `struct sockaddr_in` 转为不方便的 `struct sockaddr`
+ `iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);`
+ 服务端接收数据
if (-1 != iSocketClient)
{
iClientNum++;
printf("Get connect from client %d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
if (!fork())
{
/* 子进程的源码 */
while (1)
{
/* 接收客户端发来的数据并显示出来 */
iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient);
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
}
}
}
}
+ `signal(SIGCHLD,SIG_IGN);` 显式的忽略子进程退出信号,防止 `fork` 子进程退出后变为僵尸进程。 当然也可以根据需要设置一个信号处理函数。
+ `iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));`
+ 客户端发送数据
if (-1 == iRet)
{
printf("connect error!\n");
return -1;
}
while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
## 8_3 udp 编程
+ 服务端接收数据
while (1)
{
iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
}
}
+ 客户端发送数据
while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
iAddrLen = sizeof(struct sockaddr);
iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0,
(const struct sockaddr *)&tSocketServerAddr, iAddrLen);
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
# 串口
## 10_3 tty 体系设备节点
+ tty 终端,ubuntu 直接切换的就是 tty,
+ `tty0` 特指前台终端,如果说当前切换到了 `tty3`,那么 `tty0` 也是指向 `tty3`.
+ 可以使用 `echo hello > /dev/tty3` 来向 `tty3` 发消息,也可以用 `echo hello > /dev/tty0` 向前台发消息。
+ `while [ 1 ]; do echo msg_to_front > /dev/tty0; sleep 3; done` 来间隔3秒向前台发信息。就算切换 tty,也可以自动在当前前台显示出来。
+ `/dev/tty` 不含数字的 tty,表示当前 shell 所对应的终端,如果当前是 tty3, 那么 访问 `/dev/tty` 就是访问 `/dev/tty3`.
+ 串口对应的 tty,一般是 `ttyS0` 或者 `ttymxc0` 等等。
+ `console` 表示控制台,比 tty 权限大。 正常情况下,一般是选择某个 tty 作为 console.
+ 内核启动时,可以用 `cmdline` 来指定,比如 `console=tty1`, 如果有多个值的时候,以最后一个为准。 **注意: `console=tty0` 和 `console=tty` 都表示使用前台作为 console.
+ 可以通过 `cat /proc/cmdline` 来获取 `cmdline` 的参数设置。 如果 `cmdline` 中没有指定 console,那么默认就是第一个 `tty`,根据驱动安装的先后,可能是第一个串口,也有可能是第一个虚拟终端。
+ 在 reboot 之后,可以按住 `esc` 进入系统选择界面,选择 advance 选项,把 linux 这一行中的 quiet 去掉,然后增加 `console=tty0` , 然后退出选择第一个进入系统。 系统启动过程中 console 是等于前台,等到系统启动完成,才会按照指定的的 console 来对应设备。
+ console 也是可以用 `echo hello > /dev/console` 来打印信息。
## 10_4 tty 驱动程序框架
+ [The TTY demystified](http://www.linusakesson.net/programming/tty/), [解密TTY](https://www.cnblogs.com/liqiuhao/p/9031803.html)
+ 行编辑。大多数用户都会在输入时犯错,所以退格键会很有用。这当然可以由应用程序本身来实现,但是根据UNIX设计“哲学”,应用程序应尽可能保持简单。为了方便起见,操作系统提供了一个编辑缓冲区和一些基本的编辑命令(退格,清除单个单词,清除行,重新打印),这些命令在行规范(line discipline)内默认启用。高级应用程序可以通过将行规范设置为原始模式(raw mode)而不是默认的成熟或准则模式(cooked and canonical)来禁用这些功能。大多数交互程序(编辑器,邮件客户端,shell,及所有依赖curses或readline的程序)均以原始模式运行,并自行处理所有的行编辑命令。行规范还包含字符回显和回车换行(译者注:\r\n 和 \n)间自动转换的选项。如果你喜欢,可以把它看作是一个原始的内核级sed(1)