【Linux应用编程实战】常见函数应用
介绍一些Linux应用编程实战遇到的,常见要用的函数,进行概况总结。
目录
main()
lseek()
poll()
- struct pollfd 结构体
- 返回值
- 典例
mmap()
munmap()
ioctl()
main 函数
Linux 应用程序中,main 函数也是作为应用程序的入口函数存在,main 函数的形参一般会有两种写法:
//示例代码 1.4.1 main 函数写法之无传参
int main(void)
{
/* 代码 */
}
//示例代码 1.4.2 main 函数写法之有传参
int main(int argc, char **argv)
{
/* 代码 */
}
/*
int argc: 表示传入参数的个数,即命令行参数的数量,包括应用程序自身**路径**和**程序名**;。
char **argv: 是一个指向参数数组的指针,每个元素是一个指向参数字符串的指针
*/
例:运行当前目录下的 hello 可执行文件,并且传入参数,如下所示:
./hello 112233
那么此时参数个数为 2,并且这些参数都是作为字符串的形式传递给 main 函数:
argv[0]等于"./hello"
argv[1]等于"112233"
有传参时 main 函数的写法并不只有这一种
lseek函数
-
功能:设置当前读写位置
-
对于打开的文件,系统都会记录读写位置偏移量,记录文件当前的读写位置;
-
调用 read()或 write()函数对文件进行读写操作时,从当前读写位置(起始位置)偏移量开始进行数据读写
函数原型与头文件
/*man 2 lseek*/
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
函数参数
fd: 文件描述符。
offset: 偏移量,以字节为单位。
whence: 用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):
SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算);
SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。
返回值
成功将返回从文件头部开始算起的位置偏移量(字节为单位),也就是当前的读写位置;发生错误将返回-1。
典例
if (0 > lseek(pfd.fd, 0, SEEK_SET)) //将读位置移动到头部
poll函数
- 与 select()函数很相似,但函数接口有所不同;
- select()中,提供三个 fd_set 集合,在每个集合中添加我们关心的文件描述符;
- poll()中,则需构造一个 struct pollfd 类型数组,每个数组元素指定一个文件描述符以及我们对该文件描述符所关心的条件(数据可读、可写或异常情况)
原型
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
参数:
fds:指向一个 struct pollfd 类型的数组,数组中的每个元素都会指定一个文件描述符以及我们对该文件
描述符所关心的条件,稍后介绍 struct pollfd 结构体类型。
nfds:参数 nfds 指定了 fds 数组中的元素个数,数据类型 nfds_t 实际为无符号整形。
timeout:该参数与 select()函数的 timeout 参数相似,用于决定 poll()函数的阻塞行为,具体用法如下:
⚫ 如果 timeout 等于-1,则 poll()会一直阻塞(与 select()函数的 timeout 等于 NULL 同), 直到 fds数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号时返回。
⚫ 如果 timeout 等于 0,poll()不会阻塞,只是执行一次检查看看哪个文件描述符处于就绪态。
⚫ 如果 timeout 大于 0,则表示设置 poll()函数阻塞时间的上限值,意味着 poll()函数最多阻塞 timeout毫秒,直到 fds 数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号为止。
struct pollfd 结构体
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
-
fd 是一个文件描述符,struct pollfd 结构体中的 events 和 revents 都是位掩码;
-
调用者初始化 events 来指定需要为文件描述符 fd 做检查的事件:
当 poll()函数返回时,revents 变量由 poll()函数内部进行设置,用于说明文件描述符 fd 发生了哪些事件(注意,poll()没有更改 events 变量),我们可以对 revents 进行检查,判断文件描述符 fd 发生了什么事件。
-
应将每个数组元素的 events 成员设置为表 13.2.1 中所示的一个或几个标志,多个标志通过位或运算符( | )组合起来,通过这些值告诉内核我们关心的是该文件描述符的哪些事件。
返回时,revents 变量由内核设置为表 13.2.1 中所示的一个或几个标志:
第一组标志(POLLIN、POLLRDNORM、POLLRDBAND、POLLPRI、POLLRDHUP)与数据可读相关;
第二组标志(POLLOUT、POLLWRNORM、POLLWRBAND)与可写数据相关;
第三组标志(POLLERR、POLLHUP、POLLNVAL)是设定在 revents 变量中用来返回有关文件描述符的附加信息,
如果在 events 变量中指定了这三个标志,则会被忽略。
-
如果我们对某个文件描述符上的事件不感兴趣,则可将 events 变量设置为 0;
-
另外,将 fd 变量设置为文件描述符的负值(取文件描述符 fd 的相反数-fd),将导致对应的 events 变量被 poll()忽略,并且 revents变量将总是返回 0,这两种方法都可用来关闭对某个文件描述符的检查。
在实际应用编程中,一般用的最多的还是 POLLIN 和 POLLOUT。
返回值
poll()函数返回值含义与 select()函数的返回值是一样:
⚫ 返回-1 表示有错误发生,并且会设置 errno。
⚫ 返回 0 表示该调用在任意一个文件描述符成为就绪态之前就超时了。
⚫ 返回一个正整数表示有一个或多个文件描述符处于就绪态了,
返回值表示 fds 数组中返回的 revents变量不为 0 的 struct pollfd 对象的数量。
典例
使用 poll()函数来实现 I/O 多路复用操作,同时读取键盘和鼠标;
/*示例代码 13.2.3 使用 poll 实现同时读取鼠标和键盘*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#define MOUSE "/dev/input/event3"
int main(void)
{
char buf[100];
int fd, ret = 0, flag;
int loops = 5;
struct pollfd fds[2];
/* 打开鼠标设备文件 */
fd = open(MOUSE, O_RDONLY | O_NONBLOCK);
if (-1 == fd)
{
perror("open error");
exit(-1);
}
/* 将键盘设置为非阻塞方式 */
flag = fcntl(0, F_GETFL); //先获取原来的 flag
flag |= O_NONBLOCK; //将 O_NONBLOCK 标准添加到 flag
fcntl(0, F_SETFL, flag); //重新设置 flag
/* 同时读取键盘和鼠标 */
fds[0].fd = 0;
fds[0].events = POLLIN; //只关心数据可读
fds[0].revents = 0;
fds[1].fd = fd;
fds[1].events = POLLIN; //只关心数据可读
fds[1].revents = 0;
while (loops--)
{
ret = poll(fds, 2, -1);
if (0 > ret)
{
perror("poll error");
goto out;
}
else if (0 == ret)
{
fprintf(stderr, "poll timeout.\n");
continue;
}
/* 检查键盘是否为就绪态 */
if(fds[0].revents & POLLIN)
{
ret = read(0, buf, sizeof(buf));
if (0 < ret)
printf("键盘: 成功读取<%d>个字节数据\n", ret);
}
/* 检查鼠标是否为就绪态 */
if(fds[1].revents & POLLIN)
{
ret = read(fd, buf, sizeof(buf));
if (0 < ret)
printf("鼠标: 成功读取<%d>个字节数据\n", ret);
}
}
out:
/* 关闭文件 */
close(fd);
exit(ret);
}
struct pollfd 结构体的 events 变量和 revents 变量都是位掩码,所以可以使用"revents & POLLIN"按位与的方式来检查是否发生了相应的 POLLIN 事件,判断鼠标或键盘数据是否可读
总结
-
在使用 select()或 poll()时需要注意一个问题:
当监测到某一个或多个文件描述符成为就绪态(可以读或写)时:
需要执行相应的 I/O 操作,以清除该状态,否则该状态将会一直存在;
譬如示例代码 13.2.1 中,调用 select()函数监测鼠标和键盘这两个文件描述符; 当 select()返回时,通过 FD_ISSET()宏判断文件描述符上是否可执行 I/O 操作; 如果可以执行 I/O 操作时,应在应用程序中对该文件描述符执行 I/O 操作,以清除文件描述符的就绪态; 如果不清除就绪态,那么该状态将会一直存在,那么下一次调用 select()时,文件描述符已经处于就绪态了,将直接返回。
同理对于 poll()函数来说亦是如此,譬如示例代码 13.2.3,当 poll()成功返回时,检查文件描述符是否称为就绪态,如果文件描述符上可执行 I/O 操作时,也需要对文件描述符执行 I/O 操作,以清除就绪状态。
mmap()
定义:用于内存映射的系统调用函数
通常用于将文件或设备映射到进程的地址空间,以便对其进行读取和写入操作。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr
:指定映射的起始地址,通常传入NULL表示由系统自动分配。length
:指定映射的长度,以字节为单位。prot
:指定内存映射区域的保护方式,比如可读、可写、可执行等。flags
:指定映射区域的属性,比如映射文件、映射匿名内存等。fd
:指定要映射的文件描述符,如果映射匿名内存则传入-1。offset
:指定文件映射的偏移量。
mmap()
函数成功时返回映射区域的起始地址,失败时返回MAP_FAILED。
munmap()
用于解除指定内存映射区域的映射关系,释放内核为该映射区域所分配的资源。
int munmap(void *addr, size_t length);
addr
:指定要解除映射的起始地址。length
:指定要解除映射的长度,通常与mmap()
时传入的长度一致。
munmap()函数成功时返回0,失败时返回-1。
示例用法
以下是一个简单的示例,展示了如何使用mmap()
函数将一个文件映射到内存中,并使用munmap()
函数解除映射关系:
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
int main() {
int fd;
char *file_data;
off_t len;
// 打开文件
fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
// 获取文件大小
len = lseek(fd, 0, SEEK_END);
// 映射文件到内存
file_data = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
if (file_data == MAP_FAILED) {
perror("mmap");
return 1;
}
// 使用映射的文件数据做一些操作...
// 解除映射关系
if (munmap(file_data, len) == -1) {
perror("munmap");
return 1;
}
// 关闭文件
close(fd);
return 0;
}
上述示例中,首先打开一个文件,然后使用mmap()
函数将文件映射到内存中,并最终使用munmap()
函数解除映射关系。在实际应用中,需要注意内存映射的参数设置和错误处理,以确保操作的正确性和稳定性。
ioctl()
ioctl
函数的全称是"input/output control",是一个系统调用函数;
通过ioctl
函数,用户可以向设备发送特定的命令(ioctl命令),以实现对设备的配置、控制和查询等操作。
原型
int ioctl(int fd, unsigned long request, ...);
fd
:设备文件描述符或者套接字描述符。request
:ioctl命令,用于指定具体的操作。...
:可选参数,用于传递ioctl命令需要的参数。
ioctl()函数成功时返回0,失败时返回-1。
使用ioctl
函数的步骤
-
打开设备文件或者套接字,获取文件描述符。
-
调用
ioctl
函数,传入文件描述符、ioctl命令以及可能需要的参数。 -
常用的 request 包 括FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO
他们分别为获取可变参数(fb_var_screeninfo),设置可变参、获取固定参(struct fb_fix_screeninfo)
若第二个参数是FBIOGET_VSCREENINFO或FBIOGET_FSCREENINFO: 需提前定义一个括号内对应结构体的指针的地址作为第三个参数,函数调用成功,设备信息都在结构体内 如果第二个参数是FBIOPUT_VSCREENINFO: 则没有第三个参数;
示例用法
以下是一个简单的示例,展示了如何使用ioctl
函数向设备发送命令:
/*
首先打开设备文件`/dev/mydevice`,
然后使用`ioctl`函数向设备发送`MY_IOCTL_COMMAND`命令,并**传递一个整型参数`value`**。
通过这种方式,用户可以**根据具体的设备需求调用相应的ioctl命令**,实现对**设备的控制和配**置。
需要注意的是,**具体的ioctl命令和参数取决于设备的类型和驱动程序的实现**,
因此在使用`ioctl`函数时需要参考相应的文档或者驱动程序源码。
*/
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
int main() {
int fd;
int ret;
int value = 1;
// 打开设备文件
fd = open("/dev/mydevice", O_RDWR);
if (fd == -1) {
perror("open");
return 1;
}
// 发送ioctl命令
ret = ioctl(fd, MY_IOCTL_COMMAND, &value);
if (ret == -1) {
perror("ioctl");
return 1;
}
// 关闭设备文件
close(fd);
return 0;
}