Linux 高级IO

news2024/11/29 20:53:01

目录

    • 传统艺能😎
    • 概念😍
    • 高级IO😂
    • 五种 IO 模型👌
      • 阻塞 IO🙌
      • 非阻塞 IO😍
      • 信号驱动IO😊
      • 多路转接IO😂
    • 异步IO😘
    • 同步通信 VS 异步通信😁
    • 同步通信 VS 同步与互斥🤣
    • 阻塞 VS 非阻塞😒
    • 阻塞IO😍
    • 非阻塞IO😒
      • fcntl函数🙌
      • 实现SetNonBlock函数🙌
      • 以非阻塞轮询方式读取标准输入👌

传统艺能😎

小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦
在这里插入图片描述
1319365055

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,满怀希望,所向披靡,打码一路向北
一个人的单打独斗不如一群人的砥砺前行
这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我


在这里插入图片描述

概念😍

其实在 c++ 的笔记里面我已经详细提过了,这时还是做一下书面总结:IO 即 input/output ,在冯诺依曼体系中将数据从输入设备拷贝到内存就叫做输入,将数据从内存拷贝到输出设备就叫做输出

对文件进行读写操作本质是一种IO,文件IO对应的外设是磁盘;对网络进行读写操作本质也是一种IO,网络IO对应的外设是网卡

在这里插入图片描述
O S 如何得知外设当中有数据可读? \color{red} {OS 如何得知外设当中有数据可读?} OS如何得知外设当中有数据可读?

操作系统不会主动检测外设上是否有数据就绪,这种做法一定会降低工作效率,因为大部分情况下外设都是没数据的,因此操作系统所做的大部分检测工作其实是徒劳。

系统实际采用的是中断的方式来得知外设上是否有数据就绪的,当某个外设上面有数据就绪时,该外设就会向 CPU 当中的中断控制器发送中断信号,中断控制器再根据产生的中断信号的优先级按顺序发送给 CPU。

每一个中断信号都有一个对应的中断处理程序,存储中断信号和中断处理程序映射关系的表叫做中断向量表,当CPU收到某个中断信号时就会自动停止正在运行的程序,然后根据该中断向量表执行该中断信号对应的中断处理程序,处理完毕后再返回原被暂停的程序继续运行(需要注意的是,CPU不直接和外设打交道指的是在数据层面上,而外设其实是可以直接将某些控制信号发送给 CPU 的某些控制器的)

O S 如何处理网卡读到的数据包? \color{red} {OS 如何处理网卡读到的数据包?} OS如何处理网卡读到的数据包?

操作系统任何时刻都可能收到大量数据包,因此操作系统必须将数据包管理起来。所谓的管理就是 “先描述,再组织” 原则,此时内核当中会维护一个结构叫 sk_buff,该结构就是用来管理和控制接收或发送数据包的信息的,下面给出一个简化版的 sk_buff 结构:

在这里插入图片描述

当操作系统从网卡当中读取到一个数据包后,会将该数据依次交给链路层、网络层、传输层、应用层进行解包和分用,最终将数据包中的数据交给了上层用户,那对应 sk_buff 结构具体是如何进行数据包解包和分用的呢?

当从网卡中读取到一个数据包后,就会定义出 sk_buff 结构,然后用 sk_buff 结构当中的 data 指针指向读到的数据包,并将定义出来的这个 sk_buff 与其他 sk_buff 用双链表的形式组织起来,此时操作系统对各个数据包的管理就变成了对双链表的增删查改等操作。

接下来需要将读取的数据包交给最底层的链路层处理,进行解包和分用,此时就是让 sk_buff 结构中的 mac_header 指针指向最初的数据包,然后向后读取链路层的报头,剩下的就是需要交给网络层处理的有效载荷了解包。

这时链路层就需要将有效载荷向上交付给网络层进行解包和分用了,这里所说的向上交付只是形象的说法,实际向上交付并不是直接交付数据,我们只需要让 sk_buff 结构当中的 network_header 指针,指向数据包中链路层报头之后的数据即可,然后继续向后读取网络层的报头,便完成了网络层的解包。

紧接着就是传输层对数据进行处理了,同样的道理,让 sk_buff 结构当中的 transport_header 指针,指向数据包中网络层报头之后的数据,然后继续向后读取传输层的报头,便完成了传输层的解包。传输层解包后就可以根据具体使用的传输层协议,对应将剩下的数据拷贝到 TCP 或 UDP 的接收缓冲区供用户读取即可

在这里插入图片描述

发送数据时对数据进行封装也是同样的道理,就是依次在数据前面拷贝上对应的报头,最后再将数据发送出去(UDP)或拷贝到发送缓冲区(TCP)即可。也就是说,在封装和解包的过程中,本质存储位置是没有发生变化的,实际只是在用不同指针对数据进行操作而已。

但内核中的sk_buff并不像上面那样简单:

一方面,为了保证高效的网络报文处理效率,这就要求sk_buff的结构也必须是高效的
另一方面,sk_buff 结构需要被内核协议中的各个协议共同使用,因此 sk_buff 必须能够兼容所有网络协议

因此 sk_buff 实际是非常复杂的,在云服务器中 sk_buff 结构定义如下(带伙看个大概乐子就行了,实在太多了):

struct sk_buff {
#ifdef __GENKSYMS__
	/* These two members must be first. */
	struct sk_buff          *next;
	struct sk_buff          *prev;
	ktime_t         tstamp;
#else
	union {
		struct {
			/* These two members must be first. */
			struct sk_buff          *next;
			struct sk_buff          *prev;

			union {
				ktime_t         tstamp;
				struct skb_mstamp skb_mstamp;
				__RH_KABI_CHECK_SIZE_ALIGN(ktime_t a,
				struct skb_mstamp b);
			};
		};
		struct rb_node  rbnode; /* used in netem, ip4 defrag, and tcp stack */
	};
#endif
	struct sock             *sk;
	struct net_device       *dev;

	/*
	* This is the control buffer. It is free to use for every
	* layer. Please put your private variables there. If you
	* want to keep them across layers you have to do a skb_clone()
	* first. This is owned by whoever has the skb queued ATM.
	*/
	char                    cb[48] __aligned(8);

	unsigned long           _skb_refdst;
#ifdef CONFIG_XFRM
	struct  sec_path        *sp;
#endif
	unsigned int            len,
		data_len;
	__u16                   mac_len,
		hdr_len;
	union {
		__wsum          csum;
		struct {
			__u16   csum_start;
			__u16   csum_offset;
		};
	};
	__u32                   priority;
	kmemcheck_bitfield_begin(flags1);
	__u8                    RH_KABI_RENAME(local_df, ignore_df) :1,
	cloned : 1,
		 ip_summed : 2,
				 nohdr : 1,
					 nfctinfo : 3;
	__u8                    pkt_type : 3,
	fclone : 2,
		 ipvs_property : 1,
					 peeked : 1,
						  nf_trace : 1;
	kmemcheck_bitfield_end(flags1);
	__be16                  protocol;

	void(*destructor)(struct sk_buff *skb);
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
	struct nf_conntrack     *nfct;
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
	struct nf_bridge_info   *nf_bridge;
#endif

	/* fields enclosed in headers_start/headers_end are copied
	* using a single memcpy() in __copy_skb_header()
	*/
	/* private: */
	RH_KABI_EXTEND(__u32    headers_start[0])
		/* public: */

		int                     skb_iif;

	RH_KABI_REPLACE(__u32   rxhash,
		__u32   hash)

		__be16                  vlan_proto;
	__u16                   vlan_tci;

#ifdef CONFIG_NET_SCHED
	__u16                   tc_index;       /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
	__u16                   tc_verd;        /* traffic control verdict */
#endif
#endif

	__u16                   queue_mapping;
	kmemcheck_bitfield_begin(flags2);
#ifdef CONFIG_IPV6_NDISC_NODETYPE
	__u8                    ndisc_nodetype : 2;
#endif
	__u8                    pfmemalloc : 1;
	__u8                    ooo_okay : 1;
	__u8                    RH_KABI_RENAME(l4_rxhash, l4_hash) :1;
	__u8                    wifi_acked_valid : 1;
	__u8                    wifi_acked : 1;
	__u8                    no_fcs : 1;
	__u8                    head_frag : 1;
	/* Indicates the inner headers are valid in the skbuff. */
	__u8                    encapsulation : 1;
	RH_KABI_EXTEND(__u8                     encap_hdr_csum : 1)
		RH_KABI_EXTEND(__u8                     csum_valid : 1)
		RH_KABI_EXTEND(__u8                     csum_complete_sw : 1)
		RH_KABI_EXTEND(__u8                     xmit_more : 1)
		RH_KABI_EXTEND(__u8                     inner_protocol_type : 1)
		RH_KABI_EXTEND(__u8                     remcsum_offload : 1)
		/* 0/2 bit hole (depending on ndisc_nodetype presence) */
		kmemcheck_bitfield_end(flags2);

#if defined CONFIG_NET_DMA_RH_KABI || defined CONFIG_NET_RX_BUSY_POLL || defined CONFIG_XPS
	union {
		unsigned int    napi_id;
		RH_KABI_EXTEND(unsigned int     sender_cpu)
			RH_KABI_DEPRECATE(dma_cookie_t, dma_cookie)
	};
#endif
#ifdef CONFIG_NETWORK_SECMARK
	__u32                   secmark;
#endif
	union {
		__u32           mark;
		__u32           dropcount;
		__u32           reserved_tailroom;
	};

#ifdef __GENKSYMS__
	__be16                  inner_protocol;
#else
	union {
		__be16          inner_protocol;
		__u8            inner_ipproto;
	};
#endif

	__u16                   inner_transport_header;
	__u16                   inner_network_header;
	__u16                   inner_mac_header;
	__u16                   transport_header;
	__u16                   network_header;
	__u16                   mac_header;

	RH_KABI_EXTEND(kmemcheck_bitfield_begin(flags3))
		RH_KABI_EXTEND(__u8     csum_level : 2)
		RH_KABI_EXTEND(__u8     rh_csum_pad : 1)
		RH_KABI_EXTEND(__u8     rh_csum_bad_unused : 1) /* one bit hole */
		RH_KABI_EXTEND(__u8     offload_fwd_mark : 1)
		RH_KABI_EXTEND(__u8     sw_hash : 1)
		RH_KABI_EXTEND(__u8     csum_not_inet : 1)
		RH_KABI_EXTEND(__u8     dst_pending_confirm : 1)
		RH_KABI_EXTEND(__u8     offload_mr_fwd_mark : 1)
		/* 7 bit hole */
		RH_KABI_EXTEND(kmemcheck_bitfield_end(flags3))

		/* private: */
		RH_KABI_EXTEND(__u32    headers_end[0])
		/* public: */

		/* RHEL SPECIFIC
		*
		* The following padding has been inserted before ABI freeze to
		* allow extending the structure while preserve ABI. Feel free
		* to replace reserved slots with required structure field
		* additions of your backport, eventually moving the replaced slot
		* before headers_end, if it need to be copied by __copy_skb_header()
		*/
		u32                     rh_reserved1;
	u32                     rh_reserved2;
	u32                     rh_reserved3;
	u32                     rh_reserved4;
	union {
		unsigned int    napi_id;
		RH_KABI_EXTEND(unsigned int     sender_cpu)
			RH_KABI_DEPRECATE(dma_cookie_t, dma_cookie)
	};
#endif
#ifdef CONFIG_NETWORK_SECMARK
	__u32                   secmark;
#endif
	union {
		__u32           mark;
		__u32           dropcount;
		__u32           reserved_tailroom;
	};

#ifdef __GENKSYMS__
	__be16                  inner_protocol;
#else
	kmemcheck_bitfield_begin(flags1);
	__u8                    RH_KABI_RENAME(local_df, ignore_df) :1,
	cloned : 1,
		 ip_summed : 2,
				 nohdr : 1,
					 nfctinfo : 3;
	__u8                    pkt_type : 3,
	fclone : 2,
		 ipvs_property : 1,
					 peeked : 1,
						  nf_trace : 1;
	kmemcheck_bitfield_end(flags1);
	__be16                  protocol;

	void(*destructor)(struct sk_buff *skb);
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
	struct nf_conntrack     *nfct;
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
	struct nf_bridge_info   *nf_bridge;
#endif

	/* fields enclosed in headers_start/headers_end are copied
	* using a single memcpy() in __copy_skb_header()
	*/
	/* private: */
	/* private: */
	RH_KABI_EXTEND(__u32    headers_start[0])
		/* public: */

		int                     skb_iif;

	RH_KABI_REPLACE(__u32   rxhash,
		__u32   hash)


		__be16                  vlan_proto;
	__u16                   vlan_tci;

#ifdef CONFIG_NET_SCHED
	__u16                   tc_index;       /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
	__u16                   tc_verd;        /* traffic control verdict */
#endif
#endif

	__u16                   queue_mapping;
	kmemcheck_bitfield_begin(flags2);
#ifdef CONFIG_IPV6_NDISC_NODETYPE
	__u8                    ndisc_nodetype : 2;
#endif
	__u8                    pfmemalloc : 1;
	__u8                    ooo_okay : 1;
	__u8                    RH_KABI_RENAME(l4_rxhash, l4_hash) :1;
	__u8                    wifi_acked_valid : 1;
	__u8                    wifi_acked : 1;
	__u8                    no_fcs : 1;
	__u8                    head_frag : 1;
	/* Indicates the inner headers are valid in the skbuff. */
	__u8                    encapsulation : 1;
	RH_KABI_EXTEND(__u8                     encap_hdr_csum : 1)
		RH_KABI_EXTEND(__u8                     csum_valid : 1)
		RH_KABI_EXTEND(__u8                     csum_valid : 1)
		RH_KABI_EXTEND(__u8                     csum_complete_sw : 1)
		RH_KABI_EXTEND(__u8                     xmit_more : 1)
		RH_KABI_EXTEND(__u8                     inner_protocol_type : 1)
		RH_KABI_EXTEND(__u8                     remcsum_offload : 1)
		/* 0/2 bit hole (depending on ndisc_nodetype presence) */
		kmemcheck_bitfield_end(flags2);

#if defined CONFIG_NET_DMA_RH_KABI || defined CONFIG_NET_RX_BUSY_POLL || defined CONFIG_XPS
	union {
		unsigned int    napi_id;
		RH_KABI_EXTEND(unsigned int     sender_cpu)
			RH_KABI_DEPRECATE(dma_cookie_t, dma_cookie)
	};
#endif
#ifdef CONFIG_NETWORK_SECMARK
	__u32                   secmark;
#endif
	union {
		__u32           mark;
		__u32           dropcount;
		__u32           reserved_tailroom;
	};

#ifdef __GENKSYMS__
	__be16                  inner_protocol;
#else
	union {
		__be16          inner_protocol;
		__u8            inner_ipproto;
	};
#endif

	__u16                   inner_transport_header;
	__u16                   inner_network_header;
	__u16                   inner_mac_header;
	__u16                   transport_header;
	__u16                   network_header;
	__u16                   mac_header;

	RH_KABI_EXTEND(kmemcheck_bitfield_begin(flags3))
		RH_KABI_EXTEND(__u8     csum_level : 2)
		RH_KABI_EXTEND(__u8     rh_csum_pad : 1)
		RH_KABI_EXTEND(__u8     rh_csum_bad_unused : 1) /* one bit hole */
		RH_KABI_EXTEND(__u8     offload_fwd_mark : 1)
		RH_KABI_EXTEND(__u8     sw_hash : 1)
		RH_KABI_EXTEND(__u8     csum_not_inet : 1)
		RH_KABI_EXTEND(__u8     dst_pending_confirm : 1)
		RH_KABI_EXTEND(__u8     offload_mr_fwd_mark : 1)
		/* 7 bit hole */
		RH_KABI_EXTEND(kmemcheck_bitfield_end(flags3))

		/* private: */
		RH_KABI_EXTEND(__u32    headers_end[0])
		/* public: */

		/* RHEL SPECIFIC
		*
		* The following padding has been inserted before ABI freeze to
		* allow extending the structure while preserve ABI. Feel free
		* to replace reserved slots with required structure field
		* additions of your backport, eventually moving the replaced slot
		* before headers_end, if it need to be copied by __copy_skb_header()
		*/
		u32                     rh_reserved1;
	u32                     rh_reserved2;
	u32                     rh_reserved3;
	u32                     rh_reserved4;

	/* These elements must be at the end, see alloc_skb() for details.  */
	sk_buff_data_t          tail;
	sk_buff_data_t          end;
	unsigned char           *head,
		*data;
	unsigned int            truesize;
	atomic_t                users;
};

高级IO😂

首先 IO 主要分为两步:

  1. 等,即等待IO条件就绪
  2. 拷贝,也就是当IO条件就绪后将数据拷贝到内存或外设

任何 IO 过程都包含 “等” 和 “拷贝” 这两个步骤,但在实际的应用场景中 “等” 消耗的时间往往比 “拷贝” 消耗的时间多得多,因此要让 IO 变得高级,最核心的办法就是尽量减少 “等” 的时间!

五种 IO 模型👌

IO 中存在 5 中基本模型:

  1. 阻塞IO(死等)
  2. 非阻塞IO(周期性检测)
  3. 信号驱动IO(信号探测)
  4. IO多路转接(设置多个鱼竿钓鱼)
  5. 异步IO (找人代等,自己跑路)

阻塞IO、非阻塞IO和信号驱动IO本质上是不能提高IO的效率的,但非阻塞IO和信号驱动IO能提高整体做事的效率。

阻塞 IO🙌

阻塞IO就是在内核将数据准备好之前,系统调用会一直等待:
在这里插入图片描述
阻塞IO是最常见的IO模型,所有的套接字,默认都是阻塞方式

比如当调用 recvfrom 函数从某个套接字上读取数据时,可能底层数据还没有准备好,此时就需要等待数据就绪,当数据就绪后再将数据从内核拷贝到用户空间,最后 recvfrom 函数才会返回。

在 recvfrom 函数等待数据就绪期间,在用户看来该进程或线程就阻塞住了,本质就是操作系统将该进程或线程的状态设置为了某种非 R 状态,然后将其放入等待队列当中,当数据就绪后操作系统再将其从等待队列当中唤醒,然后将数据从内核拷贝到用户空间。以阻塞方式进行 IO 操作的进程或线程,在 “等” 和 “拷贝” 期间都不会返回,在用户看来就像是阻塞住了,因此我们称之为阻塞IO

非阻塞 IO😍

非阻塞IO是内核还没有将数据准备好时,内核调用会直接返回,并返回 EWOULDBLOCK 错误码:
在这里插入图片描述

非阻塞IO往往需要程序员以循环的方式反复尝试读写文件描述符,这个过程称为轮询,这对CPU来说是较大的浪费,一般只有特定场景下才使用。

比如当调用 recvfrom 函数以非阻塞方式从某个套接字上读取数据时,如果底层数据还没有准备好,那么 recvfrom
函数会立马错误返回,而不会让该进程或线程进行阻塞等待。
因为没有读取的数据,因此该进程或线程后续还需要继续调用 recvfrom 函数,检测底层数据是否就绪,没有就绪则继续错误返回,直到某次检测到底层数据就绪后,再将数据从内核拷贝到用户空间然后进行成功返回。

每次调用 recvfrom 函数读取数据时,就算底层数据没有就绪,recvfrom 函数也会立马返回,在用户看来该进程或线程就没有被阻塞住,因此我们称之为非阻塞IO。

阻塞IO和非阻塞IO的区别在于,阻塞IO当数据没有就绪时,后续检测数据是否就绪的工作是由操作系统发起的,而非阻塞IO当数据没有就绪时,后续检测数据是否就绪的工作是由用户发起的。

信号驱动IO😊

信号驱动IO就是当内核将数据准备好的时候,使用 SIGIO 信号通知应用程序进行IO操作:

在这里插入图片描述
当底层数据就绪的时候会向当前进程或线程递交 SIGIO 信号,因此可以通过 signalsigaction 函数将 SIGIO 信号处理程序自定义为需要进行的 IO 操作,当底层数据就绪时就会自动执行对应的IO操作。

比如我们需要调用 recvfrom 函数从某个套接字上读取数据,那么就可以将该操作定义为 SIGIO 的信号处理程序。当底层数据就绪时,操作系统就会递交 SIGIO 信号,此时就会自动执行我们定义的信号处理程序,进程将数据从内核拷贝到用户空间。

信号的产生是异步的,但信号驱动IO是同步IO的一种。

信号的产生异步的,因为信号在任何时刻都可能产生,但因为当底层数据就绪时,当前进程或线程需要停下正在做的事情,转而进行数据的拷贝操作,因此当前进程或线程仍然需要参与IO过程。判断一个IO过程是同步的还是异步的,本质就是看当前进程或线程是否需要参与IO过程,如果要参与那就是同步IO,否则就是异步IO。

多路转接IO😂

IO 多路转接也叫做IO多路复用,能够同时等待多个文件描述符的就绪状态:

在这里插入图片描述
因为 IO 过程分为 “等” 和 “拷贝” ,因此我们使用 recvfrom 等接口的底层实际上都做了两件事

  1. 当数据不就绪时需要等
  2. 第二件事就是当数据就绪后需要进行拷贝

虽然recvfrom等接口也有 “等” 的能力,但这些接口一次只能“等”一个文件描述符上的数据或空间就绪,这样 IO 效率太低了。因此系统为我们提供了三组接口,分别叫做 select、poll 和 epoll这些接口的核心工作就是“等”,我们可以将所有“等”的工作都交给这些多路转接接口。

因为多路转接接口是一次“等”多个文件描述符的,因此能够将“等”的时间重叠,当数据就绪后再调用对应的 recvfrom 等函数进行数据的拷贝,此时这些函数就能够直接拷贝,而不需要进行 “等” 了。

IO多路转接就像现实生活中演唱会帮人排队的黄牛,因为多路转接接口实际并没有帮我们进行数据拷贝的操作。这些排队黄牛可以一次帮多个人排队,此时就将多个人排队的时间进行了重叠!

异步IO😘

异步IO就是由内核在数据拷贝完成时,通知应用程序:
在这里插入图片描述

进行异步IO需要调用一些特定接口,调用后会立马返回,因为异步 IO 不需要你进行 “等” 和 “拷贝” ,这两个动作都由操作系统来完成,你要做的只是发起 IO,当 IO 完成后操作系统会通知应用程序,因此进行异步 IO 的进程或线程并不参与 IO 的所有细节!

同步通信 VS 异步通信😁

同步和异步关注的是消息通信机制

所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回,但是一旦返回调用,就能得到返回值;换句话说,就是由调用者主动等待这个调用的结果

异步则是相反,调用在发出之后,这个调用就直接返回了,所有没有返回结果;换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

为什么非阻塞IO在没有得到结果之前就返回了?

IO 分为“等”和“拷贝”,当调用 recvfrom 进行非阻塞IO时,如果数据没有就绪,那么调用会直接返回,此时这个调用返回时并没有完成一个完整的IO过程,即便调用返回了那也是属于错误的返回。因此后续还需要继续调用 recvfrom,轮询检测数据是否就绪,这才是完整的IO过程。

因此,在进行非阻塞IO时,在没有得到结果之前,虽然这个调用会返回,但后续还需要继续进行轮询检测,因此可以理解成调用还没有返回,而只有当某次轮询检测到数据就绪并且完成数据拷贝后才认为调用返回了。

同步通信 VS 同步与互斥🤣

在多进程和多线程当中有同步与互斥的概念,当看到“同步”这个词的时候,一定要先明确这个同步是同步通信的同步,还是同步与互斥的同步, 因为两者是完全不相干的概念! \color{red} {因为两者是完全不相干的概念!} 因为两者是完全不相干的概念!

进程/线程同步指在保证数据安全的前提下,让进程/线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,谈论的是进程/线程间的一种工作关系。

而同步IO指的是进程/线程与操作系统之间的关系,谈论的是进程/线程是否需要主动参与IO过程

阻塞 VS 非阻塞😒

阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)时的状态。

阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回;非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

阻塞IO😍

系统中大部分的接口都是阻塞式接口,可以用 read 函数从标准输入当中读取数据:

#include <iostream>
#include <unistd.h>
#include <fcntl.h>

int main()
{
	char buffer[1024];
	while (true){
		ssize_t size = read(0, buffer, sizeof(buffer)-1);
		if (size < 0){
			std::cerr << "read error" << std::endl;
			break;
		}
		buffer[size] = '\0';
		std::cout << "echo# " << buffer << std::endl;
	}
	return 0;
}

程序运行后,如果不进行输入操作,此时进程就会阻塞,根本原因就是因为此时底层数据不就绪,read 函数需要进行阻塞等待,一旦我们进行了输入,此时 read 就会检测到数据就绪,然后从内核读取数据再拷贝到我们传入的 buffer 数组中,并且将读取到的数据输出到显示器上面:

在这里插入图片描述

非阻塞IO😒

打开文件时默认都是以阻塞的方式打开的,如果要以非阻塞的方式打开某个文件,需要使用 open 函数打开文件时携带O_NONBLOCKO_NDELAY选项,此时就能够以非阻塞的方式打开文件,我们在打开文件时设置非阻塞方式:
在这里插入图片描述
如果想将已经打开的某个文件或套接字设置为非阻塞,此时就需要用到 fcntl 函数

fcntl函数🙌

函数原型如下:

int fcntl(int fd, int cmd, … /* arg */);

fd 为已经打开的文件描述符,cmd 为需要进行的操作,… 是可变参数,传入的 cmd 值不同,后面追加的参数也不同。

fcntl函数常用的5种功能与其对应的 cmd 取值如下:

复制一个现有的描述符(cmd=F_DUPFD)
获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)

函数调用成功则返回值取决于具体进行的操作,调用失败则返回 -1,同时错误码会被设置。

实现SetNonBlock函数🙌

我们可以定义一个函数,该函数用于将指定文件描述符设置为非阻塞状态。

先调用 fcntl 函数获取该文件描述符对应的文件状态标记(这是一个位图),此时 fcntl 的 cmd 值为 F_GETFL。在获取到的文件状态标记上添加非阻塞标记 O_NONBLOCK,再次调用 fcntl 对文件状态标记进行设置,此时 fcntl 传入的 cmd 值变为F_SETFL

bool SetNonBlock(int fd)
{
	int fl = fcntl(fd, F_GETFL);
	if (fl < 0){
		std::cerr << "fcntl error" << std::endl;
		return false;
	}
	fcntl(fd, F_SETFL, fl | O_NONBLOCK);
	return true;
}

以非阻塞轮询方式读取标准输入👌

此时在调用 read 读取标准输入之前,调用 SetNonBlock 将 0 号文件描述符设置为非阻塞就行了

需要注意的是, read 函数以非阻塞方式读取标准输入时,如果底层数据不就绪,那么 read 就会立即返回,但会以出错的形式返回,此时错误码会被设置为EAGAINEWOULDBLOCK

因此在以非阻塞方式读取数据时,如果调用 read 时得到的返回值 -1,此时还需要通过错误码进一步进行判断;如果错误码的值是EAGAINEWOULDBLOCK,说明出错是因为底层数据还没有就绪,因此后续还应继续调用 read 进行轮询检测数据是否就绪,当数据继续时再进行数据的读取。

此外,调用 read 在读取到数据之前可能会被其他信号中断,此时 read 也会以出错的形式返回,此时的错误码会被设置为EINTR,此时应该重新执行 read 进行数据的读取:

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <cstring>
#include <cerrno>
bool SetNonBlock(int fd)
{
	int fl = fcntl(fd, F_GETFL);
	if (fl < 0){
		std::cerr << "fcntl error" << std::endl;
		return false;
	}
	fcntl(fd, F_SETFL, fl | O_NONBLOCK);
	return true;
}
int main()
{
	SetNonBlock(0);
	char buffer[1024];
	while (true){
		ssize_t size = read(0, buffer, sizeof(buffer)-1);
		if (size < 0){
			if (errno == EAGAIN || errno == EWOULDBLOCK){ //底层数据没有就绪
				std::cout << strerror(errno) << std::endl;
				sleep(1);
				continue;
			}
			else if (errno == EINTR){ //在读取数据之前被信号中断
				std::cout << strerror(errno) << std::endl;
				sleep(1);
				continue;
			}
			else{
				std::cerr << "read error" << std::endl;
				break;
			}
		}
		buffer[size] = '\0';
		std::cout << "echo# " << buffer << std::endl;
	}
	return 0;
}

因此在以非阻塞的方式读取数据时,如果调用 read 读取到的返回值为 -1,此时并不应该直接认为是在底层读取时出错了,而应该继续判断错误码,如果错误码的值为EAGAINEWOULDBLOCKEINTR则应该继续调用 read 再次进行读取

运行代码后,当我们没有输入数据时,程序就会不断调用 read 检测底层数据是否就绪,一旦进行了输入,此时 read 就会在轮询检测时检测到,紧接着立马将数据读取到从内核拷贝到我们传入的buffer数组当中,并且将数据输出到显示器。

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

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

相关文章

Java006——对第一个Java程序HelloWorld的简单认识

一、HelloWorld.java程序整体认识 public class HelloWorld { //创建一个名字叫HelloWorld的类&#xff08;Java中的类叫class&#xff09;public static void main(String[] args) {//主程序入口&#xff0c;类似C语言main函数System.out.println("He…

黑客第一步:从认识这些DOS命令开始

文章目录 一、DOS是个啥&#xff1f;二、如何启动DOS命令行&#xff1f;1. 同时按下键盘winR键&#xff0c;打开运行框2. 在运行框输入命令cmd&#xff0c;然后点击确定&#xff0c;即可进入DOS命令行 三、常用的DOS命令 一、DOS是个啥&#xff1f; DOS&#xff08;Disk Opera…

《精通特征工程》学习笔记(5):数据(特征)降维

1.数据降维 通过自动数据收集和特征生成技术&#xff0c;可以快速获取大量特征&#xff0c;但不是所有特征都是有用的。数据降维就是在保留重要信息的同时消除那些“无信息量的信息”。 “无信息量”有多种定义方法&#xff0c;PCA 关注的是线性相关性&#xff0c;假设我们将…

正态(高斯)分布什么时候等于杨辉三角(二项式)展开

&#xff08;ab&#xff09;^10的杨辉三角展开项系数是1&#xff0c;10&#xff0c;45&#xff0c;120&#xff0c;200&#xff0c;252&#xff0c;200&#xff0c;120&#xff0c;45&#xff0c;10&#xff0c;1 这些系数11项的和等于1004&#xff0c;每项除以1004&#xff0c…

数据结构与算法之美 | 递归(Recursion)

什么叫做递归&#xff1f; 递归&#xff1a;去的过程叫“递”&#xff0c;回来的过程叫“归” 递归的三个条件 条件一&#xff1a;一个问题的解可以分解为几个子问题的解 条件二&#xff1a;这个问题与分解之后的子问题&#xff0c;除了数据规模不同&#xff0c;求解思路完全…

CnOpenData全国养老机构数据

一、数据简介 养老机构指为老年人提供集中居住和照料服务的机构&#xff0c;县级以上地方人民政府民政部门负责本行政区域内养老机构的指导、监督和管理。其他有关部门依照职责分工对养老机构实施监督。 与其他服务不同的是&#xff0c;养老服务是一种全人、全员、全程服务,养老…

路径之谜 2016年国赛 深度优先搜索

目录 解题思路 AC代码&#xff1a; 题目描述 小明冒充 XX 星球的骑士&#xff0c;进入了一个奇怪的城堡。 城堡里边什么都没有&#xff0c;只有方形石头铺成的地面。 假设城堡地面是 nn 个方格。如下图所示。 按习俗&#xff0c;骑士要从西北角走到东南角。可以横向或纵向…

智能 CAN 总线/串口 RS-232485 协议转换器

能CAN/串口协议转换器LCNET Pro RS-232/485提供一路RS-485、一路RS-232和一路CAN通道&#xff0c;实现CAN与串口RS-485或RS-232之间的双向数据智能转换。每个通道独立隔离&#xff0c;每路通道采用金升阳电源模块和信号隔离芯片实现2500VDC电气隔离&#xff0c;电源输入防反设计…

NAT模式 LVS负载均衡群集部署

NAT模式 LVS负载均衡群集部署 一.部署共享存储&#xff08;NFS服务器&#xff1a;192.168.80.102&#xff09;1.关闭防火墙&#xff0c;查看是否有rpcbind和nfs-utils的包2.创建两个共享文件目录3.将共享路径及网段添加到/etc/exports中&#xff08;设置为只可读&#xff09;4.…

VulnHub项目:MONEYHEIST: CATCH US IF YOU CAN

靶机名称&#xff1a; MONEYHEIST: CATCH US IF YOU CAN 地址&#xff1a;MoneyHeist: Catch Us If You Can ~ VulnHub 这个系列是一部剧改编&#xff0c;还是挺好看的&#xff0c;大家有兴趣可以去看看&#xff01; 废话不多说&#xff0c;直接上图开始&#xff01; 渗透…

(单调栈) 496. 下一个更大元素 I——【Leetcode每日一题】

❓496. 下一个更大元素 I 难度&#xff1a;简单 nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。 给你两个 没有重复元素 的数组 nums1 和 nums2 &#xff0c;下标从 0 开始计数&#xff0c;其中 nums1 是 nums2 的子集。 对…

直播美颜技术:视频美颜sdk的快速集成与开发实践

视频美颜sdk则是直播美颜技术的重要组成部分&#xff0c;它可以帮助开发者快速集成美颜功能&#xff0c;实现直播美颜。目前已经被广大平台、主播、平台用户所应用&#xff0c;在近几年甚至成了一个极其热门的讨论话题&#xff0c;毕竟它与人们的日常拍摄生活息息相关。 一、视…

重磅:百亿人工心脏赛道再添新玩家,行业未来趋势明显

市场火热&#xff0c;资本加持 昨日&#xff0c;深圳核心医疗科技股份有限公司自主研发的Corheart 6植入式左心室辅助系统获得国家药品监督管理局批准上市。这是一枚完全国产&#xff0c;拥有完备自主知识产权的人工心脏。该产品正式获批上市&#xff0c;加之之前获批的永仁心…

C++ 参数的三种传递方式和应用参加

C 参数的三种传递方式分别是值传递、指针传递和引用传递。 值传递 值传递的实质 将实参的值&#xff08;a、b&#xff09;复制到形参(m、n)相应的存储单元中&#xff0c;即形参和实参分别占用不同的存储单元。 值传递的特点 值传递的特点是单向传递&#xff0c;即主调函数…

「C/C++」C/C++ 回调函数

✨博客主页&#xff1a;何曾参静谧的博客 &#x1f4cc;文章专栏&#xff1a;「C/C」C/C程序设计 相关术语 回调函数&#xff1a;是一种常用的编程技术&#xff0c;它可以将一个函数作为参数传递给另一个函数&#xff0c;并在后者执行过程中调用前者。回调函数通常用来处理异步…

python怎么搭建免费代理IP池,免费代理IP适合爬虫工作吗

Python可以使用一些第三方库和工具来搭建免费代理IP池。简单来说&#xff0c;搭建代理IP池的步骤如下&#xff1a; 1. 获取代理IP&#xff1a;从一些免费或付费代理IP网站上爬取并验证IP地址和端口信息。 2. 验证代理IP&#xff1a;使用代理IP访问一些网站或服务&#xff0c;验…

什么是IT服务请求管理

什么是服务请求 用户每天都会提出各种 IT 请求。它可能是对新软件的请求、旧硬件的更换、对应用程序的访问或资产组件的更改。这些请求被归类为服务请求。 服务请求是向 IT 团队发出的请求&#xff0c;以满足最终用户的需求。理想情况下&#xff0c;请求是从服务请求目录中选择…

优维低代码实践:编排优化Plus

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

管理项目-加载菜单

人事管理项目-加载菜单 引入ElementUI菜单1&#xff0e;菜单模板 引入ElementUI 数据成功访问后&#xff0c;引入ElementUI组件对数据进行渲染&#xff0c;首先引入其依赖&#xff1a; npm install element-ui -S 依赖添加成功后&#xff0c;接着在main.js中引入ElementUI&a…

G2上看看Notes/Domino

大家好&#xff0c;才是真的好。 Engageug2023正在如火如荼进行&#xff0c;今年的主题是“The Future is Now”。 开场就发布了很多Notes/Domino以及相关产品的全新路线图&#xff0c;例如今年第四季度发布的新的Domino 14会直接集成Verse邮箱支持、Nomad Web功能&#xff0…