Low-level I/O 和 File System Interface

 1. 为什么需要使用 low-level I/O,glibc 里面提到了一些情形,对大量二进制数
据进行操作,某些文件上的操作只能通过 descriptor(设置一些权限,如锁),
把一些资源传递给子进程。使用 low-level I/O 的很多函数是把前面 stream 的
那个 f 去掉了,如
int open ( const char *filename, int flags[, mode_t mode])
int creat ( const char *filename, mode_t mode)
int close ( int filedes)
ssize_t read ( int filedes, void *buffer, size_t size)
ssize_t pread ( int filedes, void *buffer, size_t size, off_t offset)
ssize_t write ( int filedes, const void *buffer, size_t size)
ssize_t pwrite ( int filedes, const void *buffer, size_t size, off_t offset)
off_t lseek ( int filedes, off_t offset, int whence)
一些不同在于,这些函数对 file descriptor 操作(就是一个 int)。文件打开的权限
可以为 O_RDONLY,O_WRONLY,O_RDWR,但是 POSIX.1 下面最好用 O_READ,
O_WRITE,O_EXEC,这是所谓的 access modes,另外还有 Open-time flags,如
O_CREAT(不存在就创建),O_EXCL(和前者同时使用表示存在即报错),
O_NONBLOCK(打开某些设备如串口时避免等待很长时间,之后如果 I/O 需要 block 可
利用 fcntl 更改),O_NOCTTY(针对终端设备,不懂 @@),O_IGNORE_CTTY(同),
O_NOLINK(打开的文件是符号连接本身),O_NOTRANS(不进行 translate 工作),
O_TRUNC(截断原来的东西,ms w 就是干这个事情了),O_SHLOCK(共享锁,允许
其他的进程读),O_EXLOCK(排除性锁,不允许其他的读写),另外有一些 I/O
operating modes,如 O_APPEND(指向文件尾),O_ASYNC(允许异步读写,通
过 SIGIO 信号),O_FSYNC(同步写入文件),O_NOATIME(不更新 access time)。
其中 p 类型的读写表示根据到文件头的偏移进行的,另外 create 函数是 obsolete 的。
这里没有类似的 tell 函数,但是可以用 lseek (desc, 0, SEEK_CUR) 获得。
可以使用
FILE * fdopen ( int filedes, const char *opentype)
建立一个 stream 关联到该 descriptor 上,而
int fileno (FILE *stream)
int fileno_unlocked (FILE *stream)
将返回某个 stream 的 descriptor,通常 stdin、stdout 和 stderr 的 descriptor 可以用
STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO 表达。可见同一个 descriptor
上可以建立不同的 stream,这时两个 streams 其实是 linked 在一起,即具有相同的当前
文件指针(不是说 FILE*,这其实是 stream 的地址)等等。另外可以用
int dup ( int old)
int dup2 ( int old, int new)
产生 linked 在一起的 descriptor(编号不同),后者使用 new 原来的编号但使用的 old 的
信息;前者返回的最小的未使用的 descriptor 作为 old 的“复制品”。后面讲述 fcntl() 会进一
步解释这两个函数的。清理一个 stream 可以使用
int fclean (FILE *stream)
其作用基本和 fflush() 近似。

2. 快速 scatter-gather I/O,解决的问题是把从一个地方读出的数据快速传输到几个不同的
buffer 中,数据是一个一个 buffer 填满,然后下一个,直到输入结束或填满了所有的 buffer,
描述一个 buffer 使用一个 struct iovec,其成员 void *iov_base 描述缓存区地址,size_t iov_len
是缓存区大小,使用(sys/uio.h)
ssize_t readv ( int filedes, const struct iovec *vector, int count)
从 descriptor 读入到 iovec 指向的结构体所描述的缓存中,该区域一共包含 count 个缓存区。
写入使用
ssize_t writev ( int filedes, const struct iovec *vector, int count)

3. 现代操作系统多支持所谓 mmap(memory mapped)的方式快速读写文件,往往一个
page 存储一段数据,页面大小可以用
size_t page_size = (size_t) sysconf (_SC_PAGESIZE);
获得,然后将文件内容映射到一个内存区域(sys/mman.h)
void * mmap ( void *address, size_t length, int protect, int flags, int filedes, off_t offset)
void * mremap ( void *address, size_t length, size_t new_length, int flag)
读写内存后就可以把改动存储到文件中,读写完后需要
int munmap ( void *addr, size_t length)
如果需要显示将读写同步,应调用
int msync ( void *address, size_t length, int flags)
下面我们解释一下相关的参数,mmap 将 fildes 对应 offset 开始 长度为 length 的数据
映射到 address 处(希望在这里 @@),可以为 NULL,然后会分配的,protect 是
权限,有 PROT_READ、PROT_WRITE、PROT_EXEC。而 flags 表明使用的一些
其他的 MAP_PRIVATE(不写回文件),MAP_SHARED(写回文件,这样其他打开
该文件的程序会立即看见),MAP_FIXED(使用 address 否则 fail),MAP_ANON
(仅仅创建一个内存块,不与文件关联)。使用 munmap 时只会释放正好一个或多个
pages,不会释放一部分(尽管可以这么调用)。在 msync 里面的 flags 可以为 MS_SYNC
(保证写到磁盘上),MS_ASYNC(可以开始同步,但理解返回,不会 block)。
另外为了更高效的利用这种内存,可以提出一些建议
int madvise ( void *addr, size_t length, int advice)
其中,advice 可以是 MADV_NORMAL(正常处理),MADV_RANDOM(会被随机访问),
MADV_SEQUENTIAL(会被顺序访问),MADV_WILLNEED(不懂),MADV_DONTNEED(ft...)。
这部分看点 paging 再看比较合适 -.-

4. 需要检查多个输入输出是否有数据到达可以使用
int select (int nfds, fd_set *read-fds, fd_set *write-fds, fd_set *except-fds, struct timeval *timeout)
其中使用 nfds 表示有多少个 descriptor 被监视,他们放在 read-fds、write-fds 和 except-fds 中,
并用 timeout 所指向表示一个超时(过后不待 @@),宏 FD_SETSIZE 决定一个 descriptor 的集合
的大小,我们需要 fd_set 存放 descriptors,可以用以下宏操纵:
void FD_ZERO (fd_set *set)
void FD_SET (int filedes, fd_set *set)
void FD_CLR (int filedes, fd_set *set)
int FD_ISSET (int filedes, const fd_set *set)
依次表示一个空的 fd_set、添加一个 descriptor、清除一个 descriptor、测试是否属于该 fd_set。
如果超时,select 返回 0,如果出现错误、接收到 signal 则返回。

5. 同步 I/O 常用下面的函数
int sync ( void)
int fsync ( int fildes)
int fdatasync ( int fildes)

6. 异步输入输出,不同在于函数会立即返回,操作排在队列中回自动进行,其控制过程用
struct aiocb 决定,其包含控制某个读写的信息,如 descriptor、偏移等等。下面的操作
进行读写等等
int aio_read ( struct aiocb *aiocbp)
int aio_write ( struct aiocb *aiocbp)
int lio_listio ( int mode, struct aiocb * const list[], int nent, struct sigevent *sig)
int aio_error ( const struct aiocb *aiocbp)
ssize_t aio_return ( const struct aiocb *aiocbp)
int aio_fsync ( int op, struct aiocb *aiocbp)
int aio_suspend ( const struct aiocb * const list[], int nent, const struct timespec *timeout)
int aio_cancel ( int fildes, struct aiocb *aiocbp)
这些函数分别实现了读、写、批量读写、侦测错误、查看返回值、同步数据、暂停、取消
这些功能。同时还可以用 struct aioinit 结构进行优化,这需要首先用
void aio_init ( const struct aioinit *init)
然后用户设定的策略就可以被应用到后面的 AIO 上了。

6. fcntl 的功能
int fcntl ( int filedes, int command, ...)
有很多,如 F_DUPFD 复制 discriptor、F_GETFD(获得 descriptor 的 flags)、F_GETFL
(获得打开文件的 flags)、F_GETLK(获得文件的 lock)、F_GETOWN(获取进程 gid),
对应的 GET 都有 SET 版本,多了一个 F_SETLKW 表示等待完成才返回。比如前面的 dup
可以 fcntl (old, F_DUPFD, 0) 实现。文件 descriptor flags 中有 FD_CLOEXEC 表示在
exec 之后是否关闭。

7. 一些其他的控制使用
int ioctl ( int filedes, int command, ...)
完成,如控制终端的行为等。

8. 关于目录的几个函数
char * getcwd ( char *buffer, size_t size)
char * getwd ( char *buffer)
char * get_current_dir_name ( void)
这三个都是获得目录,中间一个最好不用,其实使 buffer 为 NULL 等价第三个,都是
malloc 出来需要 free 的。下面的是用于改变当前目录:
int chdir ( const char *filename)
int fchdir ( int filedes)
注意这里面很多 f 开头的却不是 stream 都是用的 descriptor。
为了处理一般的目录里面的东西,需要一个结构 dirent 描述,里面包含了诸如 d_name
名称,d_fileno(inode),d_type(类型)这些东西。其中 d_type 是 mode_t 类型,
可以用
int IFTODT (mode_t mode)
mode_t DTTOIF ( int dtype)
互相转化(宏)。首先必须打开一个目录,这使用的是
DIR * opendir ( const char *dirname)
它创建了一个 stream,而
int dirfd (DIR *dirstream)
将返回它的 descriptor,然后用
struct dirent * readdir (DIR *dirstream)
int readdir_r (DIR *dirstream, struct dirent *entry, struct dirent **result)
读入需要的东西(directory entry),后者是 thread-safe 的。读完后用
int closedir (DIR *dirstream)
关闭这个流。如果需要以某种特定的方式,如依照字母序来访问一个 directory 里面的
东西,就可以使用
int scandir ( const char *dir, struct dirent ***namelist, int (*selector) ( const struct dirent *), int (*cmp) ( const void *, const void *))
其中可以用来作为 cmp 的为
int alphasort ( const void *a, const void *b)
int versionsort ( const void *a, const void *b)
这类函数,一般使用 scandir 扫描一个目录,用 select 筛选,cmp 排序,由于 dirent
是用 malloc 分配的事后需要释放。
另外有
int ftw ( const char *filename, __ftw_func_t func, int descriptors)
int nftw ( const char *filename, __nftw_func_t func, int descriptors, int flag)
其中
typedef int (*__ftw_func_t) ( const char *, const struct stat *, int)
typedef int (*__nftw_func_t) ( const char *, const struct stat *, int, struct FTW *)
其用途在于 ftw()(file tree walk)将从 filename 开始遍历整棵树,将 func 作用在每一个节点上,
其中 stat 是其 file stat,另一个 int 是文件类型。descriptors 限制最多使用的 descriptors 数目。
而 nftw 稍微不同一点在于后面的 flag 可以 OR 到所有的 func 调用的 int 上去。

9. 关于链接,可以用下面的函数
int link ( const char *oldname, const char *newname)
int symlink ( const char *oldname, const char *newname)
前者用于建立硬链接,后者是符号链接,可以用
int readlink ( const char *filename, char *buffer, size_t size)
读出符号链接的目标文件。函数
char * canonicalize_file_name ( const char *name)
返回不含有 . 或者 .. 的典则文件名。而
char * realpath ( const char *restrict name, char *restrict resolved)
类似,只是放到 resolved 里面(除非为 NULL 就和前者一样了)。
删除文件就是一个 link 的反过程
int unlink ( const char *filename)
而这是删除目录,
int rmdir ( const char *filename)
ISO C 里面定义了
int remove ( const char *filename)
它对文件目录都 work。
int rename ( const char *oldname, const char *newname)
重命名文件,
int mkdir ( const char *filename, mode_t mode)
创建目录, mode_t 声明了权限,

10. 文件权限常用 struct stat 表示,里面含有多数我们关心的信息,如 st_mode(类型、权限),
st_ino(inode),st_nlink(硬连接数),属主,大小,访问、修改、修改属性时间等。一般使用
int stat ( const char *filename, struct stat *buf)
int fstat ( int filedes, struct stat *buf)
int lstat ( const char *filename, struct stat *buf)
这其中某个函数获得,最后一个是不会 follow 符号链接的。有一系列的 macro 能够检测我们需要的
类型等,如
int S_ISDIR (mode_t m)
int S_ISCHR (mode_t m)
int S_ISBLK (mode_t m)
int S_ISREG (mode_t m)
int S_ISFIFO (mode_t m)
int S_ISLNK (mode_t m)
int S_ISSOCK (mode_t m)
int S_TYPEISMQ ( struct stat *s)
int S_TYPEISSEM ( struct stat *s)
int S_TYPEISSHM ( struct stat *s)
我们可以用以下函数更改文件属主,
int chown ( const char *filename, uid_t owner, gid_t group)
可以用下面函数设置 umask
mode_t umask (mode_t mask)
调用该函数设置了 umask 同时会返回原来的 umask。可以直接用
mode_t getumask ( void)
获得 umask,
int chmod ( const char *filename, mode_t mode)
int fchmod ( int filedes, int mode)
改变文件权限,可以用
int access ( const char *filename, int how)
测试权限。如果只需要文件的时间信息,可以用
int utime ( const char *filename, const struct utimbuf *times)
该结构 times 中含有访问时间和修改时间,
int utimes ( const char *filename, struct timeval tvp[2])
int lutimes ( const char *filename, struct timeval tvp[2])
int futimes ( int *fd, struct timeval tvp[2])
将结果存在 sys/time.h 中的 timeval 类型中,l 表示不 follow 符号链接。
使用
int truncate ( const char *filename, off_t length)
int ftruncate ( int fd, off_t length)
将会截断文件,使用
int mknod ( const char *filename, int mode, int dev)
创建一些设备相关的文件,这需要更深入的了解。

11. 下面是一些创建临时文件的函数,可以避免使用固定文件可能造成的问题
FILE * tmpfile ( void)
char * tmpnam ( char *result)
char * tmpnam_r ( char *result)
char * tempnam ( const char *dir, const char *prefix)
char * mktemp ( char *template)
int mkstemp ( char *template)
其中 tmpfile 直接返回了一个 stream,后面的 nam(e) 系列仅仅返回文件名,
长度由 L_tmpnam 控制,tempnam 最强大(设定路径以及前缀),而 mktemp
依照 template 中 XXXX(以之结尾)的个数产生对应的字符串,而 s 版本返回的
是 descriptor 了。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个基于STM32 HAL库的PWMGenerator2Level例程,可用于生成2级PWM信号: ```c /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "stm32f4xx_hal.h" /* Private variables ---------------------------------------------------------*/ TIM_HandleTypeDef htim1; /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM1_Init(void); /* Private user code ---------------------------------------------------------*/ int main(void) { /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM1_Init(); /* USER CODE BEGIN 2 */ HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); //启动PWM通道1 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); //启动PWM通道2 uint16_t period = 1000; //设置PWM周期为1000 uint16_t duty1 = 500; //设置PWM通道1的占空比为50% uint16_t duty2 = 250; //设置PWM通道2的占空比为25% __HAL_TIM_SET_AUTORELOAD(&htim1, period); //设置PWM周期 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, duty1); //设置PWM通道1的占空比 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, duty2); //设置PWM通道2的占空比 /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLM = 8; RCC_OscInitStruct.PLL.PLLN = 50; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 4; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK) { Error_Handler(); } } /** * @brief TIM1 Initialization Function * @param None * @retval None */ static void MX_TIM1_Init(void) { /* USER CODE BEGIN TIM1_Init 0 */ /* USER CODE END TIM1_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; /* USER CODE BEGIN TIM1_Init 1 */ /* USER CODE END TIM1_Init 1 */ htim1.Instance = TIM1; htim1.Init.Prescaler = 99; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 999; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; if (HAL_TIM_Base_Init(&htim1) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } if (HAL_TIM_PWM_Init(&htim1) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_PWM2; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(); } sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM1_Init 2 */ /* USER CODE END TIM1_Init 2 */ } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOH_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ while(1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ ``` 需要注意的是,此例程中使用了TIM1通用定时器,并分别使用了其PWM输出通道1和通道2来生成2级PWM信号。在while循环中,没有任何操作,因为PWM信号已经被成功生成并输出。如果需要修改PWM周期和占空比,请修改以下变量: ```c uint16_t period = 1000; //设置PWM周期为1000 uint16_t duty1 = 500; //设置PWM通道1的占空比为50% uint16_t duty2 = 250; //设置PWM通道2的占空比为25% ``` 其中period表示PWM周期,duty1和duty2分别表示PWM通道1和通道2的占空比。需要注意的是,占空比的范围是0~period之间的整数。如果需要将占空比设置为50%,则可以将duty1设置为period/2。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值