Linux高性能服务器编程 重点注意 read系统调用和传入传出参数
1.read系统调用
原型
1 | ssize_t read(int fd, void *buf, size_t count); |
参数
- int fd:文件描述符,是一个指向要读取文件的指针或引用。通过打开文件或设备获得文件描述符。
- void *buf:指向用户提供的缓冲区,该缓冲区用于存储从文件中读取的数据。该缓冲区应足够大,以容纳请求读取的字节数(由count参数指定)。
- size_t count:指定要从文件中读取的字节数。这是read系统调用尝试从文件中读取的最大字节数。
返回值
- 成功读取:如果读取成功,read系统调用返回实际读取的字节数。这个返回值可能小于请求的字节数(count),通常发生在以下情况:
- 文件末尾(EOF):在到达文件末尾之前,没有读取到请求的字节数。此时,返回值将小于count值,但仍然大于0。
- 非阻塞I/O或信号中断:如果读取操作被非阻塞I/O或信号等中断,返回值也可能小于请求的字节数。
- 到达文件末尾:如果已到达文件的末尾,并且没有更多的数据可以读取,read系统调用返回0。
- 读取错误:如果读取过程中发生错误(例如,由于无效的文件描述符、磁盘故障等),read系统调用返回-1,并设置全局变量errno以指示错误的类型。errno是一个标准的错误码,用于指示系统调用失败的具体原因。
返回-1时的错误码
EAGAIN或EWOULDBLOCK这两个是重点
当read系统调用返回-1时,表示读取数据时发生了错误。此时,全局变量errno会被设置为一个特定的错误码,以指示错误的类型。以下是一些常见的错误类型及其对应的情况:
- EINTR(Interrupted system call):
- 情况:读取操作被信号中断。
- 处理:可以重新调用read函数进行重试,以确保读取操作完成。例如,使用循环来重试读取操作,直到成功或遇到其他类型的错误。
EAGAIN或EWOULDBLOCK
(Resource temporarily unavailable):——-重点- 情况:文件描述符设置为非阻塞模式,但没有可用的数据。
- 处理:可以稍后再次尝试读取,或者使用select、poll等函数来等待文件描述符变为可读。
- EIO(Input/output error):
- 情况:发生了硬件错误或其他输入/输出错误。
- 处理:根据具体情况进行错误处理或修复硬件问题。可能需要检查磁盘驱动器、网络连接等硬件设备的状态。
- EBADF(Bad file descriptor):
- 情况:指定的文件描述符无效或未打开以备读取。
- 处理:检查文件描述符是否有效,并确保在调用read之前已经正确打开文件。
- EINVAL(Invalid argument):
- 情况:传递给read函数的参数无效,如文件描述符不是有效的打开文件、缓冲区指针为空、请求的字节数为负数等。
- 处理:检查传递给read函数的参数,确保它们都是有效的。
- 其他错误码:
- 情况:可能包括文件系统错误、权限不足等。
- 处理:根据具体的错误码使用perror函数打印错误信息,并根据错误信息进行适当的处理。
需要注意的是,errno的值是在发生错误时由系统设置的,因此在调用read函数后应立即检查其返回值,并在返回值为-1时检查errno的值以确定错误的类型。
在处理这些错误时,应根据具体的错误类型和情况采取相应的措施。例如,对于可恢复的错误(如EINTR),可以重新尝试读取操作;对于资源暂时不可用的错误(如EAGAIN),可以等待一段时间后再尝试读取或使用其他同步机制;对于硬件错误或严重的系统错误(如EIO),可能需要进行更复杂的错误处理或系统恢复操作。
2.传入参数,传出参数和传入传出参数
- 传入参数(输入参数)
- 定义:
- 传入参数是指在函数(或方法、过程等,以下统称函数)调用时,从外部传递给函数的数据。这些参数的值在函数内部被使用,用于完成函数的功能,但函数一般不会修改传入参数本身在外部的原始值。
- 数据类型示例:
- 可以是基本数据类型,如整数(
int
)、浮点数(float
)、字符(char
)或字符串(string
)等。例如,一个计算圆面积的函数double calculateCircleArea(double radius)
,其中radius
就是传入参数,它的类型是双精度浮点数(double
),用于将圆的半径传递给函数,函数内部根据这个半径值来计算面积。 - 也可以是复杂的数据结构,如数组(
array
)、结构体(struct
)、对象(object
)等。例如,一个函数void printStudentInfo(Student student)
用于打印学生信息,Student
是一个结构体类型,其中包含学生的姓名、年龄、成绩等成员,student
作为传入参数,将一个学生对象的信息传递给函数,函数内部读取这些信息并进行打印。
- 可以是基本数据类型,如整数(
- 定义:
- 传出参数(输出参数)
- 定义:
- 传出参数主要用于将函数内部计算得到的结果或者数据传递回调用者。函数会对传出参数进行赋值或者修改,使得调用者可以获取函数内部产生的数据。通常,传出参数在函数调用前已经在调用者的作用域中被定义,但它的值在函数调用后才被赋予有意义的内容。
- 数据类型示例:
- 基本数据类型同样可以作为传出参数。例如,一个函数
int divide(int dividend, int divisor, int* quotient)
用于实现除法运算,其中quotient
是一个指向整数的指针,作为传出参数。函数内部通过计算dividend
除以divisor
,将商赋值给*quotient
,从而将计算结果传递回调用者。 - 对于更复杂的数据结构,如数组,也经常被用作传出参数。例如,一个函数
void getFileContents(char* filename, char* buffer, int* length)
用于读取文件内容到一个缓冲区buffer
中,并通过length
返回读取的长度。这里buffer
和length
都是传出参数,其中buffer
是字符数组,用于存储文件内容,length
是一个整数指针,用于返回读取内容的长度。
- 基本数据类型同样可以作为传出参数。例如,一个函数
- 定义:
- 传入传出参数(输入 / 输出参数)
- 定义:
- 传入传出参数兼具传入参数和传出参数的特点。在函数调用时,它首先作为传入参数将外部的数据传递给函数,函数在执行过程中会对这个参数进行修改或者更新,最后将修改后的值传递回调用者。这种参数在函数内部和外部的状态都会发生改变。
- 数据类型示例:
- 以链表节点的插入函数为例,
void insertNode(ListNode** head, ListNode* newNode)
。这里ListNode*
是一个链表节点类型,head
是一个指向链表头节点指针的指针,它作为传入传出参数。在函数调用时,head
传递了链表的头节点指针的地址,函数内部根据这个指针地址可以修改头节点(比如插入新节点作为新的头节点),并且这种修改会影响到函数外部的链表结构。 - 类似地,对于一些需要在函数内部和外部共享状态的数据结构,如共享内存中的数据块,也可以通过传入传出参数来进行操作。假设一个函数
void updateSharedData(SharedData* data)
用于更新共享内存中的数据,SharedData
是一个自定义的结构体类型,代表共享数据。函数内部对data
进行修改,这些修改会直接反映在函数外部的共享数据结构中。
- 以链表节点的插入函数为例,
- 定义:
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Darlingの妙妙屋!
评论