CAN/CANFD
CCP/UDS
Bootloader/OTA
ECU/VCU/FCU
Simulink/ECUCoder
Ethernet
Hardware
Download
上一篇
下一篇
Linux系统编程参考手册
一.操作文件
-1.打开文件与关闭文件
-2.读文件与写文件
-3.读写位置偏移量
-4.读写文件示例
-5.stat获取文件属性
-6.检查与修改文件权限
-7.remove删除文件
-8.rename重命名文件
-9.操作目录
二.字符串处理
-1.strlen计算字符串长度
-2.strcat字符串拼接
-3.strcpy字符串拷贝
-4.strcmp字符串比较
-5.strchr字符串查找
-6.字符串转换为整数
-7.字符串转换为小数
三.系统信息与系统资源
-1.获取系统信息
-2.系统时间
-3.clock进程时间
-4.产生随机数
-5.休眠
四.线程
-1.创建线程与终止线程
-2.pthread_join回收线程
-3.取消线程
-4.pthread_detach分离线程
-5.线程同步
回到顶部
Linux系统编程参考手册
一.操作文件
-1.打开文件与关闭文件
-2.读文件与写文件
-3.读写位置偏移量
-4.读写文件示例
-5.stat获取文件属性
-6.检查与修改文件权限
-7.remove删除文件
-8.rename重命名文件
-9.操作目录
二.字符串处理
-1.strlen计算字符串长度
-2.strcat字符串拼接
-3.strcpy字符串拷贝
-4.strcmp字符串比较
-5.strchr字符串查找
-6.字符串转换为整数
-7.字符串转换为小数
三.系统信息与系统资源
-1.获取系统信息
-2.系统时间
-3.clock进程时间
-4.产生随机数
-5.休眠
四.线程
-1.创建线程与终止线程
-2.pthread_join回收线程
-3.取消线程
-4.pthread_detach分离线程
-5.线程同步
回到顶部
# Linux系统编程参考手册 ## 一.操作文件
Linux操作文件经常需要使用的函数如下表: | 函数名称 | 函数功能 | | -------- | ------------------------------ | | fopen | 打开或创建文件。 | | fclose | 关闭文件。 | | fread | 读取文件数据。 | | fwrite | 将数据写入文件。 | | fseek | 设置文件读写位置偏移量。 | | ftell | 获取文件当前的读写位置偏移量。 | | stat | 获取文件属性。 | | access | 检查文件权限。 | | chmod | 修改文件权限。 | | remove | 删除文件。 | | rename | 重命名文件。 | ### 1.打开文件与关闭文件
**fopen打开文件** fopen()函数用于打开或创建文件,fopen()函数原型如下: ```c #include
FILE *fopen(const char *pathname, const char *mode); ``` fopen()函数参数: pathname:参数pathname指向文件路径,可以是绝对路径、也可以是相对路径。 mode:参数mode指定了对该文件的读写权限,是一个字符串,说明如下表。 | mode | 说明 | |:----:| --------------------------------------------------------------------------------------------------------- | | r | 以只读方式打开文件。 | | r+ | 以可读、可写方式打开文件。 | | w | 以只写方式打开文件,如果参数path指定的文件存在,将文件长度截断为0;如果指定文件不存在则创建该文件。 | | w+ | 以可读、可写方式打开文件,如果参数path指定的文件存在,将文件长度截断为0;如果指定文件不存在则创建该文件。 | | a | 以只写方式打开文件,打开以进行追加内容(在文件末尾写入),如果文件不存在则创建该文件。 | | a+ | 以可读、可写方式打开文件,以追加方式写入(在文件末尾写入),如果文件不存在则创建该文件。 | fopen()函数返回值:调用成功返回一个指向FILE类型对象的指针(FILE *),该指针与打开或创建的文件相关联,后续的标准I/O操作将围绕FILE指针进行。如果失败则返回NULL,并设置errno以指示错误原因。 **fclose关闭文件** fclose()函数用于关闭文件,fclose()函数原型如下: ```c #include
int fclose(FILE *stream); ``` fclose()函数参数:stream为FILE类型指针,指向对应的文件。 fclose()函数返回值:调用成功返回0;失败将返回EOF(也就是-1),并且会设置errno来指示错误原因。 ### 2.读文件与写文件
**fread读文件** fread()函数用于读取文件数据,fread()函数原型如下: ```c #include
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); ``` fread()函数参数: ptr:fread()函数将读取到的数据存放在参数ptr指向的缓冲区中。 size:fread()函数从文件读取nmemb个数据项,每一个数据项的大小为size个字节,所以总共读取的数据大小为nmemb * size个字节。 nmemb:参数nmemb指定了读取数据项的个数。 stream:FILE指针,指向对应的文件。 fread()函数返回值:调用成功时返回读取到的数据项的数目(数据项数目并不等于实际读取的字节数,除非参数size等于1);如果发生错误或到达文件末尾,则fread()返回的值将小于参数nmemb。fread()不能区分文件结尾和错误。 **fwrite写文件** fwrite()函数用于将数据写入文件,fwrite()函数原型如下: ```c #include
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); ``` fwrite()函数参数: ptr:fwrite()函数将参数ptr指向的缓冲区中的数据写入到文件中。 size:参数size指定了每个数据项的字节大小,与fread()函数的size参数意义相同。 nmemb:参数nmemb指定了写入的数据项个数,与fread()函数的nmemb参数意义相同。 stream:FILE指针,指向对应的文件。 fwrite()函数返回值:调用成功时返回写入的数据项的数目(数据项数目并不等于实际写入的字节数,除非参数size等于1);如果发生错误,则fwrite()返回的值将小于参数nmemb(或者等于0)。 ### 3.读写位置偏移量
**fseek设置偏移量** fseek()函数用来设置文件读写位置偏移量,fseek()函数原型如下: ```c #include
int fseek(FILE *stream, long offset, int whence); ``` fseek()函数参数: stream:FILE指针,指向对应的文件。 offset:偏移量,以字节为单位。 whence:用于定义参数offset偏移量对应的参考值,该参数为下列其中一种(宏定义): | whence | 说明 | | -------- | --------------------------------------------------------------------------------------------------------------------------------- | | SEEK_SET | 读写偏移量将指向offset字节位置处(从文件头部开始算)。 | | SEEK_CUR | 读写偏移量将指向当前位置偏移量 + offset字节位置处,offset可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移。 | | SEEK_END | 读写偏移量将指向文件末尾 + offset字节位置处,同样offset可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。 | fseek()函数返回值:成功返回0;发生错误将返回-1。 **ftell获取偏移量** ftell()函数用来获取文件当前的读写位置偏移量,ftell()函数原型如下: ```c #include
long ftell(FILE *stream); ``` fclose()函数参数:stream为FILE类型指针,指向对应的文件。 fclose()函数返回值:函数调用成功将返回当前读写位置偏移量;调用失败将返回-1,并会设置errno以指示错误原因。 ### 4.读写文件示例
读文件示例: ```c #include
#include
int main(void) { char buf[100] = {0}; FILE *fp = NULL; int ret = 0; printf("Hello! Using the fopen and fread function.\n"); if(NULL == (fp = fopen("./test_file", "r"))) { perror("fopen error"); exit(-1); } printf("fopen test file successfully!\n"); /* 使用fseek()和ftell()函数获取文件大小,先将读写位置移动到文件末尾 */ if (0 > fseek(fp, 0, SEEK_END)) { perror("fseek error"); fclose(fp); exit(-1); } ret = ftell(fp); //获取当前位置偏移量=文件大小 if(0 > ret) { perror("ftell error"); fclose(fp); exit(-1); } printf("ftell test file successfully! %d bytes.\n", ret); if (0 > fseek(fp, 0, SEEK_SET)) //为了读取文件,重新将读写位置移到文件开头处 { perror("fseek error"); fclose(fp); exit(-1); } printf("fseek test file set successfully!\n"); if(ret > fread(buf, 1, ret, fp)) { perror("fread error"); fclose(fp); exit(-1); } printf("fread test file successfully!\n"); printf("Read %d bytes: %s", ret, buf); fclose(fp); exit(0); } ``` 写文件示例: ```c #include
#include
int main(void) { char buf[] = "Hello!\n"; FILE *fp = NULL; printf("Hello! Using the fopen and fwrite function.\n"); /* 打开文件 */ if(NULL == (fp = fopen("./test_file", "w+"))) { perror("fopen error"); exit(-1); } printf("fopen test file successfully!\n"); /* 写入数据 */ if(sizeof(buf) > fwrite(buf, 1, sizeof(buf), fp)) { perror("fwrite error"); fclose(fp); exit(-1); } printf("fwrite test file successfully!\n"); /* 关闭文件 */ fclose(fp); exit(0); } ``` ### 5.stat获取文件属性
stat()函数用来获取文件属性,stat()函数原型如下: ```c #include
#include
#include
int stat(const char *pathname, struct stat *statbuf); ``` stat()函数参数: pathname:用于指定一个需要查看属性的文件路径。 statbuf:struct stat类型指针,用于指向一个struct stat结构体变量。调用stat函数的时候需要传入一个struct stat变量的指针,获取到的文件属性信息就记录在struct stat结构体中。 stat()函数返回值:成功返回0;失败返回-1,并设置errno。 struct stat是内核定义的一个结构体,在
头文件中申明,所以可以在应用层使用,这个结构体中的所有元素加起来构成了文件的属性信息,结构体内容如下所示: ```c struct stat { dev_t st_dev; /* 文件所在设备的ID */ ino_t st_ino; /* 文件对应inode节点编号 */ mode_t st_mode; /* 文件对应的模式 */ nlink_t st_nlink; /* 文件的链接数 */ uid_t st_uid; /* 文件所有者的用户ID */ gid_t st_gid; /* 文件所有者的组ID */ dev_t st_rdev; /* 设备号(指针对设备文件) */ off_t st_size; /* 文件大小(以字节为单位) */ blksize_t st_blksize; /* 文件内容存储的块大小 */ blkcnt_t st_blocks; /* 文件内容所占块数 */ struct timespec st_atim; /* 文件最后被访问的时间 */ struct timespec st_mtim; /* 文件内容最后被修改的时间 */ struct timespec st_ctim; /* 文件状态最后被改变的时间 */ } ``` st_mode是struct stat结构体中的一个成员变量,是一个32位无符号整形数据,该变量记录了文件的类型、文件的权限这些信息。可以使用如下代码st.st_mode & S_IFMT获取文件类型,Linux共有七种文件类型,如下表所示: | 类型宏 | 文件类型 | 说明 | | -------- | ------------ | --------------------------------------------------- | | S_IFREG | 普通文件 | 常规文件,包含文本文件与二进制文件。 | | S_IFDIR | 目录文件 | 文件夹,文件夹在Linux系统中也是一种文件。 | | S_IFCHR | 字符设备文件 | 对应于字符硬件设备。 | | S_IFBLK | 块设备文件 | 对应于块硬件设备。 | | S_IFLNK | 符号链接文件 | 指向另一个文件路径的文件,类似于Windows的快捷方式。 | | S_IFIFO | 管道文件 | 管道文件用于进程间通信。 | | S_IFSOCK | 套接字文件 | 套接字文件用于网络通信。 | struct timespec结构体定义在
头文件中,是Linux系统中时间相关的结构体,获取时间之后通常需要使用localtime_r()函数、strftime()函数将时间转换为易于理解的格式。 获取文件属性示例: ```c #include
#include
#include
#include
#include
int main(void) { struct stat file_stat; struct tm file_tm; char time_str[100]; int ret; /* 获取文件属性,使用stat函数,读取属性存放到stat结构体 */ ret = stat("./test_file", &file_stat); if (-1 == ret) { perror("stat error"); exit(-1); } /* 打印文件大小,使用stat结构体成员st_size */ printf("文件大小:%ld\n", file_stat.st_size); /* 判断文件类型,使用stat结构体成员st_mode */ switch (file_stat.st_mode & S_IFMT) { case S_IFREG: printf("普通文件\n"); break; case S_IFDIR: printf("目录文件\n"); break; case S_IFCHR: printf("字符设备\n"); break; case S_IFBLK: printf("块设备\n"); break; case S_IFLNK: printf("符号链接文件\n"); break; case S_IFIFO: printf("管道文件\n"); break; case S_IFSOCK: printf("套接字文件\n"); break; } /* 打印文件最后修改时间,使用stat结构体成员st_mtim */ localtime_r(&file_stat.st_mtim.tv_sec, &file_tm); strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &file_tm); printf("文件修改时间: %s\n", time_str); /* 打印文件所有者用户ID,使用stat结构体成员st_uid */ printf("文件所有者用户ID:%d\n", file_stat.st_uid); exit(0); } ``` ### 6.检查与修改文件权限
**access检查文件权限** access()函数用于检查文件权限,access()函数原型如下: ```c #include
int access(const char *pathname, int mode); ``` access()函数参数: pathname:需要进行权限检查的文件路径。 mode:该参数可以取以下值: | mode | 权限 | | ---- | ---------------------- | | F_OK | 检查文件是否存在。 | | R_OK | 检查是否拥有读权限。 | | W_OK | 检查是否拥有写权限。 | | X_OK | 检查是否拥有执行权限。 | access()函数返回值:检查项通过则返回0,表示拥有相应的权限并且文件存在;否则返回-1,并设置errno。 **chmod修改文件权限** chmod()函数用于修改文件权限,chmod()函数原型如下: ```c #include
int chmod(const char *pathname, mode_t mode); ``` chmod()函数参数: pathname:需要进行权限修改的文件路径,若该参数所指为符号链接,实际改变权限的文件是符号链接所指向的文件,而不是符号链接文件本身。 mode:该参数用于描述文件权限,可以直接使用八进制数据来描述。 chmod()函数返回值:成功返回0;失败返回-1,并设置errno。 文件权限对于文件来说是非常重要的属性,是不能随随便便被任何用户所修改的,要想更改文件权限,要么是超级用户(root)进程、要么进程有效用户ID与文件的用户ID(文件所有者)相匹配。 Linux/Unix 的文件调用权限分为三级 : 文件所有者(Owner)、用户组(Group)、其它用户(Other Users)。当使用八进制3位数字更改文件权限时,第1个数字针对文件所有者(Owner)的权限,第2个数字针对用户组(Group)的权限,第3个数字针对其它用户(Other Users)的权限。 尽管理论上存在几乎无穷种类的文件权限设置方式,在实际应用中常用3种设置: - 0755:标准程序/目录权限。所有者可读、可写、可执行(完全控制)。组用户 & 其他用户可读、可执行(可以查看内容和运行程序/进入目录,但不能修改)。0755是大多数可执行程序的标准权限。 - 0644:标准普通文件权限。所有者可读可写。组用户 & 其他用户*只读。文件不可执行。0644适用于大多数不可执行的普通文件,比如文本文件、配置文件等。通常在Linux下新建文件的权限为0644。 - 0777:完全开放权限。所有用户可读可写可执行。0777通常只用于临时测试快速排查文件权限问题。生产环境严禁使用0777。 检查与修改文件权限示例: ```c #include
#include
#include
#include
int main(void) { int ret; /* 检查文件是否存在,如果文件不存在返回-1 */ ret = access("./test_file7", F_OK); if (-1 == ret) { printf("文件不存在\n"); exit(-1); } /* 检查文件读权限,可读返回0 */ ret = access("./test_file7", R_OK); if(!ret) printf("文件可读\n"); else printf("文件不可读\n"); /* 检查文件写权限,可写返回0 */ ret = access("./test_file7", W_OK); if(!ret) printf("文件可写\n"); else printf("文件不可写\n"); /* 检查文件执行权限,可执行返回0 */ ret = access("./test_file7", X_OK); if(!ret) printf("文件可执行\n"); else printf("文件不可执行\n"); /* 修改文件权限,修改为0777 */ ret = chmod("./test_file7", 0777); if (-1 == ret) { perror("chmod error"); exit(-1); } printf("chmod 完成\n"); /* 检查文件执行权限,可执行返回0 */ ret = access("./test_file7", X_OK); if(!ret) printf("文件可执行\n"); else printf("文件不可执行\n"); exit(0); } ``` ### 7.remove删除文件
remove()函数用于删除文件,remove()函数原型如下: ```c #include
int remove(const char *pathname); ``` remove()函数参数:pathname为需要删除的文件或目录路径,可以是相对路径、也可是决定路径。 remove()函数返回值:成功返回0;失败将返回-1,并设置errno。 删除文件示例: ```c #include
#include
#include
int main(void) { int ret; /* 检查文件是否存在,如果文件不存在返回-1 */ ret = access("./test_file7", F_OK); if (-1 == ret) { printf("文件不存在。\n"); exit(-1); } /* 删除文件,删除失败则返回-1 */ ret = remove("./test_file7"); if (-1 == ret) { perror("remove error"); exit(-1); } printf("删除文件成功。\n"); exit(0); } ``` ### 8.rename重命名文件
rename()函数用于重命名文件,rename()函数原型如下: ```c #include
int rename(const char *oldpath, const char *newpath); ``` rename()函数参数: oldpath:原文件路径。 newpath:新文件路径。 rename()函数返回值:成功返回0;失败将返回-1,并设置errno。 调用rename()会将现有的一个路径名oldpath重命名为newpath参数所指定的路径名。rename()调用仅操作目录条目,而不移动文件数据(不改变文件inode编号、不移动文件数据块中存储的内容),重命名既不影响指向该文件的其它硬链接,也不影响已经打开该文件的进程(譬如,在重命名之前该文件已被其它进程打开了,而且还未被关闭)。 重命名文件示例: ```c #include
#include
#include
int main(void) { int ret; /* 检查文件是否存在,如果文件不存在返回-1 */ ret = access("./test_file7", F_OK); if (-1 == ret) { printf("文件不存在。\n"); exit(-1); } /* 重命名文件,重命名失败则返回-1 */ ret = rename("./test_file7", "./test_file8"); if (-1 == ret) { perror("rename error"); exit(-1); } printf("重命名文件成功。\n"); exit(0); } ``` ### 9.操作目录
Linux操作目录(目录也是一种文件)经常需要使用的函数如下表: | 函数名称 | 函数功能 | | -------- | ------------------ | | mkdir | 创建目录。 | | rmdir | 删除目录。 | | opendir | 打开目录。 | | readdir | 读取目录。 | | closedir | 关闭目录。 | | getcwd | 获取当前工作目录。 | **mkdir创建目录** mkdir()函数用于创建目录,mkdir()函数原型如下: ```c #include
#include
int mkdir(const char *pathname, mode_t mode); ``` mkdir()函数参数: pathname:需要创建的目录路径,该路径名可以是相对路径,也可以是绝对路径,若指定的路径名已经存在,则调用mkdir()将会失败。 mode:该参数用于描述文件权限,可以直接使用八进制数据来描述。此处文件权限与chmod()函数参数mode相同,具体可以参考chmod()函数。 mkdir()函数返回值:成功返回0;失败将返回-1,并会设置errno。 **rmdir删除目录** rmdir()函数用于删除目录,rmdir()函数原型如下: ```c #include
int rmdir(const char *pathname); ``` rmdir()函数参数:pathname为需要删除的目录对应的路径名,并且该目录必须是一个空目录。 rmdir()函数返回值:成功返回0;失败将返回-1,并会设置errno。 **opendir打开目录** opendir()函数用于打开目录,opendir()函数原型如下: ```c #include
#include
DIR *opendir(const char *name); ``` opendir()函数参数:name指定需要打开的目录路径名,可以是绝对路径,也可以是相对路径。 opendir()函数返回值:成功将返回指向该目录的句柄,一个DIR指针(其实质是一个结构体指针),后续对该目录的操作需要使用该DIR指针变量;若调用失败,则返回NULL。 **readdir读取目录** readdir()函数用于读取目录,readdir()函数原型如下: ```c #include
struct dirent *readdir(DIR *dirp); ``` readdir()函数参数:dirp为目录句柄DIR指针。 readdir()函数返回值:返回一个指向struct dirent结构体的指针,该结构体表示dirp指向的目录流中的下一个目录条目。在到达目录流的末尾或发生错误时,它返回NULL。 **closedir关闭目录** closedir()函数用于关闭目录,closedir()函数原型如下: ```c #include
#include
int closedir(DIR *dirp); ``` closedir()函数参数:dirp为目录句柄DIR指针。 closedir()函数返回值:成功返回0;失败将返回-1,并设置errno。 **getcwd获取当前目录** getcwd()函数用于获取当前目录,getcwd()函数原型如下: ```c #include
char *getcwd(char *buf, size_t size); ``` getcwd()函数参数: buf:getcwd()函数将内含当前工作目录绝对路径的字符串存放在buf缓冲区中。 size:缓冲区的大小,分配的缓冲区大小必须要大于字符串长度,否则调用将会失败。 getcwd()函数返回值:如果调用成功将返回指向buf的指针,失败将返回NULL,并设置errno。 操作目录示例: ```c #include
#include
#include
#include
#include
#include
int main(void) { int ret; char buf[100] = {0}; //用来存放绝对路径的字符串 char *ptr; //指向绝对路径字符串的指针 DIR *dirp; //目录句柄DIR指针 /* 获取当前目录,获取失败返回NULL */ ptr = getcwd(buf, sizeof(buf)); if (NULL == ptr) { perror("getcwd error"); exit(-1); } printf("当前工作目录: %s\n", buf); /* 创建目录dir1,创建失败返回-1 */ ret = mkdir("./dir1", 0755); if (-1 == ret) { perror("mkdir error"); exit(-1); } /* 打开目录dir1,打开失败返回NULL */ dirp = opendir("./dir1"); if (NULL == dirp) { perror("opendir error"); exit(-1); } /* 关闭目录dir1,关闭失败返回-1 */ closedir(dirp); if (-1 == ret) { perror("closedir error"); exit(-1); } /* 删除目录dir1,删除失败返回-1 */ rmdir("./dir1"); if (-1 == ret) { perror("rmdir error"); exit(-1); } printf("操作目录完成。\n"); exit(0); } ``` ## 二.字符串处理
Linux字符串处理经常需要使用的函数如下表: | 函数名称 | 函数功能 | | ----------------------- | ------------------ | | strlen | 计算字符串长度。 | | strcat、strncat | 字符串拼接。 | | strcpy、strncpy | 字符串拷贝。 | | strcmp、strncmp | 字符串比较。 | | strchr、strrchr、strstr | 字符串查找。 | | atoi、atol、strtol | 字符串转换为整数。 | | atof、strtof | 字符串转换为小数。 | ### 1.strlen计算字符串长度
strlen()函数用于计算字符串长度,strlen()函数原型如下: ```c #include
size_t strlen(const char *s); ``` strlen()函数参数:s为需要进行长度计算的字符串,字符串必须包含结束字符' \0 '。 strlen()函数返回值:返回字符串长度(以字节为单位),字符串结束字符' \0 '不计算在内。 在程序当中,我们通常也会使用sizeof来计算长度,那strlen和sizeof有什么区别呢? - sizeof是C语言内置的操作符关键字,而strlen是C语言库函数; - sizeof仅用于计算数据类型的大小或者变量的大小,而strlen只能以结尾为' \0 '的字符串作为参数; - 编译器在编译时就计算出了sizeof的结果,而strlen必须在运行时才能计算出来; - sizeof计算数据类型或变量会占用内存的大小,strlen计算字符串实际长度。 计算字符串长度示例: ```c #include
#include
#include
int main(void) { char s1[] = "Hello C."; printf("Hello! Using the strlen function.\n"); /* 计算字符串的长度并返回 */ printf("String length: %ld\n", strlen(s1)); printf("String sizeof: %ld\n", sizeof(s1)); exit(0); } ``` ### 2.strcat字符串拼接
strcat()函数用于字符串拼接,strcat()函数原型如下: ```c #include
char *strcat(char *dest, const char *src); char *strncat(char *dest, const char *src, size_t n); ``` strcat()函数参数: dest:目标字符串。 src:源字符串。 strncat()函数特有参数n:要追加的最大字符数。 strcat()函数返回值:返回指向目标字符串dest的指针。 strcat()函数会把src所指向的字符串追加到dest所指向的字符串末尾,所以必须要保证dest有足够的存储空间来容纳两个字符串,否则会导致溢出错误;dest末尾的' \0 '结束字符会被覆盖,src末尾的结束字符' \0 '会一起被复制过去,最终的字符串只有一个' \0 '。 strncat()与strcat()的区别在于,strncat可以指定源字符串追加到目标字符串的字符数量。如果源字符串src包含n个或更多个字符,则strncat()将n+1个字节追加到dest目标字符串(src中的n个字符加上结束字符' \0 ')。 字符串拼接示例: ```c #include
#include
#include
int main(void) { /* 被连接的字符串需要有足够的存储空间,否则会导致溢出错误 */ char s0[50] = "Hello :"; char s1[50] = "Hello :"; char s2[] = "Using the strcat and strncat function."; /* 字符串s2被附加(连接)到字符串s0 */ printf("strcat result: %s\n", strcat(s0, s2)); /* 从字符串s2最多附加(连接)20个字符到字符串s1 */ printf("strncat result: %s\n", strncat(s1, s2, 20)); /* strcat连接之后的字符串发生变化:变长 */ printf("s0 after strcat: %s\n", s0); /* strncat连接之后的字符串发生变化:变长 */ printf("s1 after strncat: %s\n", s1); exit(0); } ``` ### 3.strcpy字符串拷贝
strcpy()函数用于字符串拷贝,strcpy()函数原型如下: ```c #include
char *strcpy(char *dest, const char *src); char *strncpy(char *dest, const char *src, size_t n); ``` strcpy()函数参数: dest:目标字符串。 src:源字符串。 strncpy()函数特有参数n:从src中复制的最大字符数。 strcpy()函数返回值:返回指向目标字符串dest的指针。 strcpy()会把src(必须包含结束字符' \0 ')指向的字符串复制(包括字符串结束字符' \0 ')到dest,所以必须保证dest指向的内存空间足够大,能够容纳下src字符串,否则会导致溢出错误。 strncpy()与strcpy()的区别在于,strncpy()可以指定从源字符串src复制到目标字符串dest的字符数量,strncpy()把src所指向的字符串复制到dest,最多复制n个字符。当n小于或等于src字符串长度(不包括结束字符的长度)时,则复制过去的字符串中没有包含结束字符' \0 ';当n大于src字符串长度时,则会将src字符串的结束字符' \0 '也一并拷贝过去,必须保证dest指向的内存空间足够大,能够容纳下拷贝过来的字符串,否则会导致溢出错误。 字符串拷贝示例: ```c #include
#include
#include
int main(void) { /* 被复制的字符串需要有足够的存储空间,否则会导致溢出错误 */ char s0[50] = {0}; char s1[50] = {0}; char s00[50] = {0}; char s2[] = "Using the strcpy and strncpy function."; /* 字符串s2被复制到字符串s0 */ printf("strcpy result: %s\n", strcpy(s0, s2)); /* 从字符串s2最多复制20个字符到字符串s1 */ printf("strncpy result: %s\n", strncpy(s1, s2, 20)); printf("s00 before memcpy: %s\n", s00); printf("s00 length before memcpy: %ld\n", strlen(s00)); /* 从字符串s2最多复制20个字符到字符串s00 */ memcpy(s00, s2, 20); printf("memcpy result: %s\n", s00); printf("s00 length after memcpy: %ld\n", strlen(s00)); exit(0); } ``` 从上述示例可知,strncpy()函数的用法与memcpy()函数的用法类似,区别在于strncpy()函数只能拷贝字符串,而memcpy()函数可以复制任意内容,例如字符串、数组、结构体等。 ### 4.strcmp字符串比较
strcmp()函数用于字符串比较,strcmp()函数原型如下: ```c #include
int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); ``` strcmp()函数参数: s1:进行比较的字符串1。 s2:进行比较的字符串2。 strncmp()函数特有参数n:最多比较前n个字符。 strcmp()函数返回值: - 如果返回值小于0,则表示s1小于s2 - 如果返回值大于0,则表示s1大于s2 - 如果返回值等于0,则表示字符串s1等于字符串s2 strcmp进行字符串比较,主要是通过比较字符串中的字符对应的ASCII码值,strcmp会根据ASCII编码依次比较str1和str2的每一个字符,直到出现了不同的字符,或者某一字符串已经到达末尾(遇见了字符串结束字符' \0 ')。 字符串比较示例: ```c #include
#include
#include
int main(void) { char s1[] = "Hello C."; printf("Hello! Using the strcmp and strncmp function.\n"); /* strcmp比较字符串的大小 */ printf("相同字符串比较结果: %d\n", strcmp("ABC", "ABC")); printf("小字符串跟大字符串比较结果: %d\n", strcmp("ABC", "ABCD")); printf("大小写字符串比较结果: %d\n", strcmp("ABC", "abc")); printf("大小写字符串比较结果: %d\n", strcmp("abc", "ABC")); /* strncmp比较字符串的大小 */ printf("相同字符串比较结果: %d\n", strncmp("ABC", "ABC", 3)); printf("小字符串跟大字符串比较结果: %d\n", strncmp("ABC", "ABCD", 3)); printf("小字符串跟大字符串比较结果: %d\n", strncmp("ABC", "ABCD", 4)); printf("大小写字符串比较结果: %d\n", strncmp("ABC", "abc", 3)); exit(0); } ``` 编译运行上述代码,相同字符串比较结果为0,小字符串小于大字符串,小写字符串大于小写字符串(因为小写字母的ASCII码值比大写字母的ASCII码值更大),符合预期。 ### 5.strchr字符串查找
strchr()函数用于字符串查找,strchr()函数原型如下: ```c #include
char *strchr(const char *s, int c); char *strrchr(const char *s, int c); char *strstr(const char *haystack, const char *needle); ``` 函数参数: s:给定的目标字符串。 c:需要查找的字符。 haystack:给定的目标字符串。 needle:需要查找的子字符串。 函数返回值:返回字符(或者子字符串)第一次在字符串s中出现的位置,如果未找到字符(或者子字符串),则返回NULL。 字符串结束字符' \0 '也将作为字符串的一部分,因此,如果将参数c指定为' \0 ',则函数将返回指向结束字符的指针。strchr函数在字符串s中从前到后(或者称为从左到右)查找字符c,找到字符c第一次出现的位置就返回,返回值指向这个位置,如果找不到字符c就返回NULL。 strrchr()与strchr()函数一样,它同样表示在字符串中查找某一个字符,返回字符第一次在字符串中出现的位置,如果没找到该字符,则返回值NULL,但两者唯一不同的是,strrchr()函数在字符串中是从后到前(或者称为从右向左)查找字符,找到字符第一次出现的位置就返回,返回值指向这个位置。 与strchr()函数不同的是,strstr()可在给定的字符串haystack中查找第一次出现子字符串needle的位置,不包含结束字符' \0 '。 字符串查找示例: ```c #include
#include
#include
int main(void) { char *ptr = NULL; char s1[] = "Hello C. Hello World."; printf("Hello! Using the strchr, strrchr and strstr function.\n"); /* strchr在字符串中搜索字符,返回字符第1次出现的位置,如果未找到字符c,则返回NULL */ ptr = strchr(s1, 'o'); if (NULL != ptr) { printf("搜素字符: %c\n", *ptr); printf("strchr位置: %ld\n", ptr - s1); } /* strrchr在字符串中搜索字符,返回字符最后1次出现的位置,如果未找到字符c,则返回NULL */ ptr = strrchr(s1, 'o'); if (NULL != ptr) { printf("搜素字符: %c\n", *ptr); printf("strrchr位置: %ld\n", ptr - s1); } /* strstr在字符串中搜索字符串,返回字符串第1次出现的位置,如果未找到字符串,则返回NULL */ ptr = strstr(s1, "World"); if (NULL != ptr) { printf("搜素字符串: %s\n", ptr); printf("strstr位置: %ld\n", ptr - s1); } exit(0); } ``` strchr()函数返回字符c第一次在字符串s中出现的位置,位置编号从0开始。 ### 6.字符串转换为整数
atoi()函数用于将字符串转换为整数,atoi()函数原型如下: ```c #include
int atoi(const char *nptr); long atol(const char *nptr); ``` 函数参数:nptr为需要进行转换的字符串。 函数返回值:分别返回转换之后得到的int类型数据或者long int类型数据。 目标字符串nptr中可以包含非数字字符,转换时跳过前面的空格字符(如果目标字符串开头存在空格字符),直到遇上数字字符或正负符号才开始做转换,而再遇到非数字或字符串结束时(' /0 ')才结束转换,并将结果返回。 使用atoi()、atol()函数只能转换十进制表示的数字字符串,即0~9。如果需要转换非十进制数据,可以使用strtol()函数,strtol()函数原型如下: ```c #include
long int strtol(const char *nptr, char **endptr, int base); ``` strtol()函数参数: nptr:需要进行转换的字符串。 endptr:如果endptr不为NULL,则strtol()或strtoll()会将字符串中第一个无效字符的地址存储在endptr中。如果根本没有数字,strtol()或strtoll()会将nptr的原始值存储在endptr中(并返回0)。也可将参数endptr设置为NULL,表示不接收相应信息。 base:数字基数,参数base必须介于2和36(包含)之间,或者是特殊值0。参数base决定了字符串转换为整数时合法字符的取值范围,譬如,当base=2时,合法字符为' 0 '、' 1 '(表示是一个二进制表示的数字字符串);当base=8时,合法字符为' 0 '、' 1 '、' 2 '、' 3 '……' 7 '(表示是一个八进制表示的数字字符串);当base=16时,合法字符为' 0 ' 、' 1 '、' 2 '、' 3 '……' 9 '、' a '……' f '(表示是一个十六进制表示的数字字符串);当base大于10的时候,' a '代表10、' b '代表11、' c '代表12,依次类推,' z '代表35(不区分大小写)。在base=0的情况下,如果字符串包含一个了“0x”前缀,表示该数字将以16为基数;如果包含的是“0”前缀,表示该数字将以8为基数。 strtol()函数返回值:返回转换之后得到的long int类型数据。 需要进行转换的目标字符串可以以任意数量的空格或者0开头,转换时跳过前面的空格字符,直到遇上数字字符或正负符号(' + '或' - ')才开始做转换,而再遇到非数字或字符串结束时(' /0 ')才结束转换,并将结果返回。 ### 7.字符串转换为小数
atof()函数用于将字符串转换为小数,atof()函数原型如下: ```c #include
double atof(const char *nptr); ``` 函数参数:nptr为需要进行转换的字符串。 函数返回值:返回转换之后得到的double类型数据。 strtof()函数同样用于将字符串转换为小数,strtof()函数原型如下: ```c #include
float strtof(const char *nptr, char **endptr); double strtod(const char *nptr, char **endptr); ``` 函数参数: nptr:需要进行转换的字符串。 endptr:与strtol()函数参数endptr相同。 函数返回值:分别返回转换之后得到的float类型数据或者double类型数据。 字符串转换为数字示例: ```c #include
#include
#include
int main(void) { printf("Hello! Using the atoi, atol and strtol function.\n"); /* atoi函数将字符串转换为十进制int数据类型 */ printf("atoi %d\n", atoi("200")); /* atol函数将字符串转换为十进制long int数据类型 */ printf("atol %ld\n", atol("2000")); /* strtol函数将字符串转换为long int数据类型,支持各种进制 */ printf("十进制500 strtol: %ld\n", strtol("500", NULL, 10)); printf("八进制500 strtol: %ld\n", strtol("500", NULL, 8)); printf("十六进制500 strtol: %ld\n", strtol("500", NULL, 16)); /* atof函数将字符串转换为double数据类型 */ printf("atof %lf\n", atof("3.14")); /* strtof函数将字符串转换为float数据类型 */ printf("strtof %f\n", strtof("0.005", NULL)); exit(0); } ``` 数字转换为字符串通常使用格式化IO库函数即可,比如printf()与sprintf()函数。 ## 三.系统信息与系统资源
Linux系统信息与系统资源经常需要使用的函数如下表: | 函数名称 | 函数功能 | | ----------- | -------------------------- | | uname | 操作系统信息。 | | sysinfo | 系统统计信息。 | | time | 获取系统时间。 | | ctime_r | 将系统时间转换为字符串。 | | localtime_r | 将系统时间转换为tm结构体。 | | clock | 获取进程时间。 | | rand | 获取随机数。 | | srand | 设置随机数种子。 | | sleep | 秒级休眠。 | | usleep | 微秒级休眠。 | ### 1.获取系统信息
**uname获取操作系统信息** uname()函数用于获取操作系统信息,uname()函数原型如下: ```c #include
int uname(struct utsname *buf); ``` uname()函数参数:buf为struct utsname结构体类型指针,指向一个struct utsname结构体类型对象。 uname()函数用法非常简单,先定义一个struct utsname结构体变量,调用uname()函数时传入变量的地址即可,struct utsname结构体如下所示: ```c struct utsname { char sysname[]; /* 当前操作系统的名称 */ char nodename[]; /* 网络上的名称(主机名) */ char release[]; /* 操作系统内核版本 */ char version[]; /* 操作系统发行版本 */ char machine[]; /* 硬件架构类型 */ #ifdef _GNU_SOURCE char domainname[];/* 当前域名 */ #endif }; ``` uname()函数返回值:成功返回0;失败将返回-1,并设置errno。 **sysinfo获取系统统计信息** sysinfo()函数用于获取系统统计信息,sysinfo()函数原型如下: ```c #include
int sysinfo(struct sysinfo *info); ``` sysinfo()函数参数:info为struct sysinfo结构体类型指针,指向一个struct sysinfo结构体类型对象。 sysinfo()函数用法也非常简单,先定义一个struct sysinfo结构体变量,调用sysinfo()函数时传入变量的地址即可,struct sysinfo结构体如下所示: ```c struct sysinfo { long uptime; /* 自系统启动之后所经过的时间(以秒为单位) */ unsigned long loads[3]; /* 1, 5, and 15 minute load averages */ unsigned long totalram; /* 总的可用内存大小 */ unsigned long freeram; /* 还未被使用的内存大小 */ unsigned long sharedram; /* Amount of shared memory */ unsigned long bufferram; /* Memory used by buffers */ unsigned long totalswap; /* Total swap space size */ unsigned long freeswap; /* swap space still available */ unsigned short procs; /* 系统当前进程数量 */ unsigned long totalhigh; /* Total high memory size */ unsigned long freehigh; /* Available high memory size */ unsigned int mem_unit; /* 内存单元大小(以字节为单位) */ char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding to 64 bytes */ }; ``` sysinfo()函数返回值:成功返回0;失败将返回-1,并设置errno。 获取系统信息示例: ```c #include
#include
#include
#include
int main(void) { printf("Hello! Using the uname and sysinfo function.\n"); /* uname函数获取操作系统信息,失败将返回-1,并设置errno */ struct utsname os_info; struct sysinfo sys_info; int ret; ret = uname(&os_info); if(ret == -1){ perror("uname error"); exit(-1); } printf("操作系统名称:%s\n", os_info.sysname); printf("主机名称:%s\n", os_info.nodename); printf("操作系统版本:%s\n", os_info.version); /* sys_info函数获取系统统计信息,失败将返回-1,并设置errno */ ret = sysinfo(&sys_info); if(ret == -1){ perror("sysinfo error"); exit(-1); } printf("总内存:%lu\n", sys_info.totalram); printf("空闲内存:%lu\n", sys_info.freeram); printf("系统负载:%lu\n", sys_info.loads[0]); exit(0); } ``` 实际使用时,从utsname结构体、sysinfo结构体获取需要的系统信息即可。 ### 2.系统时间
**time获取系统当前时间** time()函数用于获取系统当前时间,以秒为单位,返回得到的值是自1970-01-01 00:00:00 +0000 (UTC)以来的秒数,其函数原型如下: ```c #include
time_t time(time_t *tloc); ``` time()函数参数tloc:如果tloc参数不是NULL,则返回值也存储在tloc指向的内存中,通常使用NULL即可。 time()函数返回值:成功则返回自1970-01-01 00:00:00 +0000 (UTC)以来的时间值(以秒为单位);失败则返回-1,并会设置errno。 所以由此可知,time函数获取得到的是一个时间段,也就是从1970-01-01 00:00:00 +0000 (UTC)到现在这段时间所经过的秒数,所以你要计算现在这个时间点,只需要使用time()得到的秒数加1970-01-01 00:00:00即可!当然,这并不需要我们手动去计算,可以直接使用相关系统调用或C库函数来得到当前时间。 **ctime_r将系统时间转换为字符串** ctime_r()函数用于将系统时间转换为字符串,ctime_r()函数原型如下: ```c #include
char *ctime_r(const time_t *timep, char *buf); ``` ctime_r()函数参数: timep:time_t时间变量指针,即需要转换的系统时间。 buf:用于存放字符串的缓冲区首地址。 ctime_r()函数返回值:成功将返回一个char *类型指针,指向转换后得到的字符串;失败将返回NULL。 ctime_r()函数转换得到的时间是计算机所在地对应的本地时间(譬如在中国对应的便是北京时间),并不是UTC时间。 **localtime_r将系统时间转换为tm结构体** localtime_r()函数用于将time()函数得到的系统时间转换为struct tm结构体,localtime_r()函数原型如下: ```c #include
struct tm *localtime_r(const time_t *timep, struct tm *result); ``` localtime_r()函数参数: timep:time_t时间变量指针,即需要转换的系统时间。 result:存放转换结果的结构体首地址。 struct tm结构体中包含了年月日时分秒星期等独立的时间信息,易于我们查看和理解,struct tm结构体如下: ```c struct tm { int tm_sec; /* 秒(0-60) */ int tm_min; /* 分(0-59) */ int tm_hour; /* 时(0-23) */ int tm_mday; /* 日(1-31) */ int tm_mon; /* 月(0-11) */ int tm_year; /* 年(这个值表示的是自1900年到现在经过的年数) */ int tm_wday; /* 星期(0-6, 星期日Sunday = 0、星期一=1…) */ int tm_yday; /* 一年里的第几天(0-365, 1 Jan = 0) */ int tm_isdst; /* 夏令时 */ }; ``` 获取时间及时间转换示例: ```c #include
#include
#include
int main(void) { printf("Hello! Using the time, ctime_r and localtime_r function.\n"); time_t tt; struct tm t; char tm_str[] = {0}; int ret; /* time函数获取系统时间(秒数),失败则返回-1 */ tt = time(NULL); if(tt == -1){ perror("time error"); exit(-1); } printf("当前系统时间(秒数):%ld\n", tt); /* ctime_r函数将系统时间(秒数)转换为字符串 */ ctime_r(&tt, tm_str); printf("当前时间:%s", tm_str); /* localtime_r函数将系统时间(秒数)转换为tm结构体 */ localtime_r(&tt, &t); printf("当前时间:%d年%d月%d日 %d:%d:%d\n", t.tm_year+1900, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); exit(0); } ``` Linux提供了很多时间转换函数,其中ctime_r()与localtime_r()这两个是最常用的。 ### 3.clock进程时间
clock()函数提供了一个简单的方式用于获取进程时间,它的返回值描述了进程使用的总的CPU时间(也就是进程时间,包括用户CPU时间和系统CPU时间),其函数原型如下所示: ```c #include
clock_t clock(void); ``` clock()函数无参数。 clock()函数返回值:返回值是到目前为止程序的进程时间,为clock_t类型,注意clock()的返回值并不是系统节拍数,如果想要获得秒数,请除以CLOCKS_PER_SEC(这是一个宏,值固定为1000000)。如果返回的进程时间不可用或其值无法表示,则该返回值是-1。 进程时间示例: ```c #include
#include
#include
int main(void) { printf("Hello! Using the clock function.\n"); clock_t t_start; clock_t t_end; int i, j; /* clock函数获取开始时间 */ t_start = clock(); if(t_start == -1){ perror("clock error"); exit(-1); } for(i=0; i<10000; i++){ for(j=0; j<10000; j++); } /* clock函数获取结束时间 */ t_end = clock(); if(t_end == -1){ perror("clock error"); exit(-1); } printf("当前系统宏CLOCKS_PER_SEC的值:%ld\n", CLOCKS_PER_SEC); printf("总的CPU时间(秒数):%f\n", (double)(t_end-t_start)/CLOCKS_PER_SEC); exit(0); } ``` ### 4.产生随机数
**rand获取随机数** rand()函数用于获取随机数,rand()函数原型如下: ```c #include
int rand(void); ``` rand()函数无参数。 rand()函数返回值:返回一个介于0到RAND_MAX(包含)之间的值,也就是数学上的[0, RAND_MAX]。 程度当中调用rand()可以得到[0, RAND_MAX]之间的伪随机数,多次调用rand()便可以生成一组伪随机树序列,但是这里有个问题,就是每一次运行程序所得到的随机数序列都是相同的,那如何使得每一次启动应用程序所得到的随机数序列是不一样的呢?那就通过设置不同的随机数种子,可通过srand()设置随机数种子。如果没有调用srand()设置随机数种子的情况下,rand()会将1作为随机数种子,如果随机数种子相同,那么每一次启动应用程序所得到的随机数序列就是一样的,所以每次启动应用程序需要设置不同的随机数种子,这样就可以使得程序每次运行所得到随机数序列不同。 **srand设置随机数种子** srand()函数用于设置随机数种子,srand()函数原型如下: ```c #include
void srand(unsigned int seed); ``` srand()函数参数:seed指定一个随机数种子,int类型的数据,通常将当前时间作为随机数种子赋值给参数seed,譬如time(NULL),因为每次启动应用程序时间上是一样的,所以就能够使得程序中设置的随机数种子在每次启动程序时是不一样的。 srand()函数无返回值。 srand()函数常用的用法srand(time(NULL))。 产生随机数示例: ```c #include
#include
#include
int main(void) { printf("Hello! Using the rand and srand function.\n"); printf("当前系统宏RAND_MAX的值:%d\n", RAND_MAX); int rand_arr[10]; /* 使用srand函数设置随机数函数 */ srand(time(NULL)); /* 使用rand函数生成伪随机数 */ for(int i=0; i<10; i++){ rand_arr[i] = rand()%100; } printf("["); for(int i=0; i<10; i++){ printf("%d ", rand_arr[i]); } printf("]\n"); exit(0); } ``` Linux提供了很多随机数相关函数,其中rand()与srand()这两个是最常用的。 ### 5.休眠
**sleep秒级休眠** sleep()函数用于秒级休眠,sleep()函数原型如下: ```c #include
unsigned int sleep(unsigned int seconds); ``` sleep()函数参数: seconds:休眠时长,以秒为单位。 sleep()函数返回值:如果休眠时长为参数seconds所指定的秒数,则返回0;若被信号中断则返回剩余的秒数。 sleep()是一个秒级别休眠函数,程序在休眠过程中,是可以被其它信号所打断的。 **usleep微秒级休眠** usleep()函数用于微秒级休眠,usleep()函数原型如下: ```c #include
int usleep(useconds_t usec); ``` usleep()函数参数: usec:休眠时长,以微秒为单位。 usleep()函数返回值:成功返回0;失败返回-1,并设置errno。 休眠示例: ```c #include
#include
#include
int main(void) { printf("Hello! Using the sleep and usleep function.\n"); printf("Sleep 1 second.\n"); /* 使用sleep函数休眠,单位为s */ sleep(1); printf("Sleep another second.\n"); /* 使用usleep函数休眠,单位为us */ usleep(1000000); printf("Sleep mode ended.\n"); exit(0); } ``` ## 四.线程
Linux线程经常需要使用的函数如下表: | 函数名称 | 函数功能 | | ---------------------- | ---------------------- | | pthread_create | 创建线程。 | | pthread_self | 获取线程ID。 | | pthread_exit | 终止线程。 | | pthread_join | 回收线程。 | | pthread_cancel | 取消线程。 | | pthread_setcancelstate | 设置线程的取消性状态。 | | pthread_testcancel | 添加取消点。 | | pthread_detach | 分离线程。 | ### 1.创建线程与终止线程
**pthread_create创建线程** pthread_create()函数用于创建线程,pthread_create()函数原型如下: ```c #include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); ``` pthread_create()函数参数: thread:pthread_t类型指针,当pthread_create()成功返回时,新创建的线程的线程ID会保存在参数thread所指向的内存中,后续的线程相关函数会使用该标识来引用此线程。 attr:pthread_attr_t类型指针,指向pthread_attr_t类型的缓冲区,pthread_attr_t数据类型定义了线程的各种属性。如果将参数attr设置为NULL,那么表示将线程的所有属性设置为默认值,以此创建新线程。 start_routine:参数start_routine是一个函数指针,指向一个函数,新创建的线程从start_routine()函数开始运行,该函数返回值类型为void *,并且该函数的参数只有一个void *,其实这个参数就是pthread_create()函数的第四个参数arg。如果需要向start_routine()传递的参数有一个以上,那么需要把这些参数放到一个结构体中,然后把这个结构体对象的地址作为arg参数传入。 arg:传递给start_routine()函数的参数。一般情况下,需要将arg指向一个全局或堆变量,意思就是说在线程的生命周期中,该arg指向的对象必须存在,否则如果线程中访问了该对象将会出现错误。当然也可将参数arg设置为NULL,表示不需要传入参数给start_routine()函数。 pthread_create()函数返回值:成功返回0;失败时将返回一个错误号,并且参数thread指向的内容是不确定的。 注意pthread_create()在调用失败时通常会返回错误码,它并不像其它库函数或系统调用一样设置errno,每个线程都提供了全局变量errno的副本,这只是为了与使用errno到的函数进行兼容,在线程中,从函数中返回错误码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局变量,这样可以把错误的范围限制在引起出错的函数中。 **pthread_self获取线程ID** pthread_self()函数用于获取线程ID,pthread_self()函数原型如下: ```c #include
pthread_t pthread_self(void); ``` pthread_self()函数无参数。 pthread_self()函数返回值:返回当前线程的线程ID,数据类型为pthread_t,在Linux系统中,使用无符号长整型(unsigned long int)来表示pthread_t数据类型。 线程ID在应用程序中非常有用,因为很多线程相关函数,譬如pthread_cancel()、pthread_detach()、pthread_join()等,它们都是利用线程ID来标识要操作的目标线程。 **pthread_exit终止线程** pthread_exit()函数用于终止线程,pthread_exit()函数原型如下: ```c #include
void pthread_exit(void *retval); ``` pthread_exit()函数参数:参数retval的数据类型为void *,指定了线程的返回值、也就是线程的退出码,该返回值可由另一个线程通过调用pthread_join()来获取;同理,如果线程是在start函数中执行return语句终止,那么return的返回值也是可以通过pthread_join()来获取的。实际使用时如果不需要线程的返回值或者退出码,则使用pthread_exit(NULL)即可。 调用pthread_exit()相当于在线程的start函数中执行return语句,不同之处在于,可在线程start函数所调用的任意函数中调用pthread_exit()来终止线程。如果主线程调用了pthread_exit(),那么主线程也会终止,但其它线程依然正常运行,直到进程中的所有线程终止才会使得进程终止。 创建线程与终止线程示例: ```c #include
#include
#include
#include
#include
static void *new_thread_start(void *arg) { printf("新线程start\n"); printf("新线程: 进程ID<%d> 线程ID<%lu>\n", getpid(), pthread_self()); sleep(1); printf("新线程end\n"); pthread_exit(NULL); } int main(void) { printf("Hello! Using the pthread_create, pthread_self and pthread_exit function.\n"); pthread_t tid; int ret; /* pthread_create函数创建线程,成功返回0 */ ret = pthread_create(&tid, NULL, new_thread_start, NULL); if(ret){ printf("错误码:%s\n", strerror(ret)); //此处不应该使用perror函数,因为perror的值可能受到其它线程干扰 exit(-1); } /* pthread_self函数获取线程ID */ printf("主线程: 进程ID<%d> 线程ID<%lu>\n", getpid(), pthread_self()); printf("主线程end\n"); /* pthread_create函数终止线程,其它线程依然正常运行 */ pthread_exit(NULL); //exit(0); 此处不应该使用exit函数,因为exit会终止整个进程,包括主线程与其它子线程 } ``` 编译运行上述示例,新线程与主线程进程ID相同而线程ID不同,主线程终止之后新线程会继续运行。如果主线程中使用了exit(0)而不是使用pthread_exit(NULL)的话,主线程会终止整个进程,包括新线程。因此,在主线程中使用pthread_exit(NULL)与exit(0)是不同的:pthread_exit(NULL)只终止主线程本身而exit(0)终止整个进程。 ### 2.pthread_join回收线程
pthread_join()函数用来阻塞等待线程的终止,并获取线程的退出码,回收线程资源,pthread_join()函数原型如下: ```c #include
int pthread_join(pthread_t thread, void **retval); ``` pthread_join()函数参数: thread:pthread_join()等待指定线程的终止,通过参数thread(线程ID)指定需要等待的线程。 retval:如果参数retval不为NULL,则pthread_join()将目标线程的退出状态(即目标线程通过pthread_exit()退出时指定的返回值或者在线程start函数中执行return语句对应的返回值)复制到retval所指向的内存区域;如果目标线程被pthread_cancel()取消,则将PTHREAD_CANCELED放在retval中。如果对目标线程的终止状态不感兴趣,则可将参数retval设置为NULL。 pthread_join()函数返回值:成功返回0;失败将返回错误码。 调用pthread_join()函数将会以阻塞的形式等待指定的线程终止,如果该线程已经终止,则pthread_join()立刻返回。如果多个线程同时尝试调用pthread_join()等待指定线程的终止,那么结果将是不确定的。 若线程并未分离,则必须使用pthread_join()来等待线程终止,回收线程资源;如果线程终止后,其它线程没有调用pthread_join()函数来回收该线程,那么该线程将变成僵尸线程。同样,僵尸线程除了浪费系统资源外,若僵尸线程积累过多,那么会导致应用程序无法创建新的线程。 当然,如果进程中存在着僵尸线程并未得到回收,当进程终止之后,进程会被其父进程回收,所以僵尸线程同样也会被回收。 回收线程示例: ```c #include
#include
#include
#include
#include
static void *new_thread_start(void *arg) { printf("新线程start\n"); sleep(3); printf("新线程end\n"); pthread_exit((void *)8); } int main(void) { printf("Hello! Using the pthread_join function.\n"); pthread_t tid; void* tret; int ret; /* pthread_create函数创建线程,成功返回0,失败返回错误码 */ ret = pthread_create(&tid, NULL, new_thread_start, NULL); if(ret){ printf("错误码:%s\n", strerror(ret)); exit(-1); } /* pthread_join函数等待tid线程执行结束,成功返回0,失败返回错误码 */ printf("pthread_join函数等待新线程执行结束。\n"); ret = pthread_join(tid, &tret); if(ret){ printf("错误码:%s\n", strerror(ret)); exit(-1); } printf("新线程终止,code = %ld\n", (long)tret); sleep(2); printf("主线程end\n"); exit(0); } ``` 上述示例使用tret存放子线程的退出状态,如果对子线程的终止状态不感兴趣,则可将pthread_join()函数的参数retval设置为NULL。 ### 3.取消线程
**pthread_cancel取消线程** pthread_cancel()函数用于取消线程,pthread_cancel()函数原型如下: ```c #include
int pthread_cancel(pthread_t thread); ``` pthread_cancel()函数参数: thread:指定需要取消的目标线程。 pthread_cancel()函数返回值:成功返回0,失败将返回错误码。 发出取消请求之后,函数pthread_cancel()立即返回,不会等待目标线程的退出。默认情况下,目标线程也会立刻退出,其行为表现为如同调用了参数为PTHREAD_CANCELED(其实就是(void *)-1)的pthread_exit()函数,但是,线程可以设置自己不被取消或者控制如何被取消(可以设置线程的取消性状态),所以pthread_cancel()并不会等待线程终止,仅仅只是提出请求。 **pthread_setcancelstate设置线程的取消性状态** pthread_setcancelstate()函数用于设置线程的取消性状态,pthread_setcancelstate()函数原型如下: ```c #include
int pthread_setcancelstate(int state, int *oldstate); ``` pthread_setcancelstate()函数参数: state:必须是以下值之一: - PTHREAD_CANCEL_ENABLE:线程可以取消,这是新创建的线程取消性状态的默认值,所以新建线程以及主线程默认都是可以取消的。 - PTHREAD_CANCEL_DISABLE:线程不可被取消,如果此类线程接收到取消请求,则会将请求挂起,直至线程的取消性状态变为PTHREAD_CANCEL_ENABLE。 oldstate:函数将线程之前的取消性状态保存在参数oldstate指向的缓冲区中,如果对之前的状态不感兴趣,Linux允许将参数oldstate设置为NULL。 pthread_setcancelstate()函数返回值:调用成功将返回0,失败返回非0值的错误码。 取消线程示例: ```c #include
#include
#include
#include
#include
static void *new_thread_start(void *arg) { /* pthread_setcancelstate函数设置线程是否可以取消,默认ENABLE可以取消 */ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); //即使不设置默认也可以取消 while(1){ printf("新线程运行中。。。\n"); sleep(2); } return (void *)0; } int main(void) { printf("Hello! Using the pthread_cancel and pthread_setcancelstate function.\n"); pthread_t tid; void* tret; int ret; /* pthread_create函数创建线程,成功返回0,失败返回错误码 */ ret = pthread_create(&tid, NULL, new_thread_start, NULL); if(ret){ printf("错误码:%s\n", strerror(ret)); exit(-1); } sleep(7); /* pthread_cancel函数向新线程发送取消请求,失败返回错误码 */ ret = pthread_cancel(tid); if(ret) { printf("错误码:%s\n", strerror(ret)); exit(-1); } /* pthread_join函数等待tid线程执行结束,成功返回0,失败返回错误码 */ printf("pthread_join函数等待新线程执行结束。\n"); ret = pthread_join(tid, &tret); if(ret){ printf("错误码:%s\n", strerror(ret)); exit(-1); } printf("新线程终止,code = %ld\n", (long)tret); sleep(3); printf("主线程end\n"); exit(0); } ``` 示例中调用pthread_cancel(tid)之后新线程立即终止,调用pthread_join(tid, &tret)回收线程时因为新线程已经终止,因此回收线程没有阻塞而是立即返回,tret的值为-1即新线程的终止状态为-1表明新线程不是经由return返回终止的而是通过取消线程终止的。 示例中for循环内部的printf()与sleep()函数均为取消点,因此新线程可以被取消。某些程序中可能没有调用到带取消点的函数导致线程不可取消,此时可以通过调用pthread_testcancel()函数来添加取消点。 **pthread_testcancel添加取消点** pthread_testcancel()函数用来添加取消点,pthread_testcancel函数原型如下: ```c #include
void pthread_testcancel(void); ``` pthread_testcancel()函数无参数,无返回值。 添加取消点示例: ```c #include
#include
#include
#include
#include
static void *new_thread_start(void *arg) { /* pthread_setcancelstate函数设置线程是否可以取消,默认ENABLE可以取消 */ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); //即使不设置默认也可以取消 printf("新线程运行中。。。\n"); while(1){ /* pthread_testcancel函数添加取消点,否则死循环中无取消点导致线程无法取消 */ pthread_testcancel(); } return (void *)0; } int main(void) { printf("Hello! Using the pthread_testcancel function.\n"); pthread_t tid; void* tret; int ret; /* pthread_create函数创建线程,成功返回0 */ ret = pthread_create(&tid, NULL, new_thread_start, NULL); if(ret){ printf("错误码:%s\n", strerror(ret)); exit(-1); } sleep(4); /* pthread_cancel函数向新线程发送取消请求 */ ret = pthread_cancel(tid); if (ret) { printf("错误码:%s\n", strerror(ret)); exit(-1); } /* pthread_join函数等待tid线程执行结束,成功返回0,失败返回错误码 */ printf("pthread_join函数等待新线程执行结束。\n"); ret = pthread_join(tid, &tret); if(ret){ printf("错误码:%s\n", strerror(ret)); exit(-1); } printf("新线程终止,code = %ld\n", (long)tret); sleep(2); printf("主线程end\n"); exit(0); } ``` 示例中的新线程是可以使用pthread_cancel(tid)来取消与终止的,新线程中如果没有调用pthread_testcancel()来添加取消点的话,while(1)死循环中无取消点导致线程无法取消与终止,新线程将一直持续运行,此时只能通过终止进程的方式来强制终止所有线程。 ### 4.pthread_detach分离线程
pthread_detach()函数用于分离线程,pthread_detach()函数原型如下: ```c #include
int pthread_detach(pthread_t thread); ``` pthread_detach()函数参数:thread指定需要分离的线程。 pthread_detach()函数返回值:调用成功将返回0;失败将返回一个错误码。 默认情况下,当线程终止时,其它线程可以通过调用pthread_join()获取其返回状态、回收线程资源,有时,程序员并不关系线程的返回状态,只是希望系统在线程终止时能够自动回收线程资源并将其移除。在这种情况下,可以调用pthread_detach()将指定线程进行分离,也就是分离线程。一个线程既可以将另一个线程分离,同时也可以将自己分离。一旦线程处于分离状态,就不能再使用pthread_join()来获取其终止状态,此过程是不可逆的,一旦处于分离状态之后便不能再恢复到之前的状态。处于分离状态的线程,当其终止后,能够自动回收线程资源。 分离线程示例: ```c #include
#include
#include
#include
#include
static void *new_thread_start(void *arg) { int ret; printf("新线程start\n"); printf("新线程分离!\n"); /* pthread_detach函数分离线程,成功返回0,失败返回错误码 */ ret = pthread_detach(pthread_self()); if(ret){ printf("错误码:%s\n", strerror(ret)); return NULL; } sleep(3); printf("新线程end\n"); pthread_exit(NULL); } int main(void) { printf("Hello! Using the pthread_detach function.\n"); pthread_t tid; void* tret; int ret; /* pthread_create函数创建线程,成功返回0,失败返回错误码 */ ret = pthread_create(&tid, NULL, new_thread_start, NULL); if(ret){ printf("错误码:%s\n", strerror(ret)); exit(-1); } sleep(1); /* pthread_join函数等待tid线程执行结束,成功返回0,失败返回错误码 */ printf("pthread_join函数等待新线程执行结束。\n"); ret = pthread_join(tid, &tret); if(ret){ //对于已经分离的线程,无法再使用pthread_join函数获取终止状态 printf("错误码: %d\n", ret); printf("错误码:%s\n", strerror(ret)); pthread_exit(NULL); //如果仅要终止主线程不终止新线程 //exit(-1); 如果要终止整个进程 } printf("新线程终止,code = %ld\n", (long)tret); sleep(2); printf("主线程end\n"); exit(0); } ``` 编译上述代码,会报错误码:Invalid argument,说明主线程调用pthread_join()确实会出错,错误提示为“Invalid argument”,因为主线程调用pthread_join()时新线程已经将自己分离了。在处理pthread_join()的报错时,如果仅要终止主线程不终止新线程则使用pthread_exit(NULL),此时主线程终止后新线程会继续运行。如果要终止整个进程则使用exit(-1),此时整个进程终止运行,主线程与新线程全部终止运行,即使新线程已经分离依然会随着进程的终止而终止。 ### 5.线程同步
Linux线程同步经常需要使用互斥锁,互斥锁相关函数如下表: | 函数名称 | 函数功能 | | --------------------- | -------------- | | pthread_mutex_init | 初始化互斥锁。 | | pthread_mutex_lock | 互斥锁上锁。 | | pthread_mutex_unlock | 互斥锁解锁。 | | pthread_mutex_destroy | 销毁互斥锁。 | **pthread_mutex_init初始化互斥锁** pthread_mutex_init()函数用于初始化互斥锁,pthread_mutex_init()函数原型如下: ```c #include
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); ``` pthread_mutex_init()函数参数: mutex:pthread_mutex_t类型指针,指向需要进行初始化操作的互斥锁对象。 attr:pthread_mutexattr_t类型指针,指向一个pthread_mutexattr_t类型对象,该对象用于定义互斥锁的属性,若将参数attr设置为NULL,则表示将互斥锁的属性设置为默认值。 pthread_mutex_init()函数返回值:成功返回0;失败将返回一个非0的错误码。 **pthread_mutex_lock互斥锁上锁** pthread_mutex_lock()函数用于互斥锁上锁,pthread_mutex_lock()函数原型如下: ```c #include
int pthread_mutex_lock(pthread_mutex_t *mutex); ``` pthread_mutex_lock()函数参数:mutex指向互斥锁对象。 pthread_mutex_lock()函数返回值:调用成功时返回0;失败将返回一个非0值的错误码。 调用pthread_mutex_lock()函数对互斥锁进行上锁,如果互斥锁处于未锁定状态,则此次调用会上锁成功,函数调用将立马返回;如果互斥锁此时已经被其它线程锁定了,那么调用pthread_mutex_lock()会一直阻塞,直到该互斥锁被解锁,到那时,调用将锁定互斥锁并返回。 **pthread_mutex_unlock互斥锁解锁** pthread_mutex_unlock()函数用于互斥锁解锁,pthread_mutex_unlock()函数原型如下: ```c #include
int pthread_mutex_unlock(pthread_mutex_t *mutex); ``` pthread_mutex_unlock()函数参数:mutex指向互斥锁对象。 pthread_mutex_unlock()函数返回值:调用成功时返回0;失败将返回一个非0值的错误码。 调用pthread_mutex_unlock()函数将已经处于锁定状态的互斥锁进行解锁。以下行为均属错误: - 对处于未锁定状态的互斥锁进行解锁操作; - 解锁由其它线程锁定的互斥锁。 **pthread_mutex_destroy销毁互斥锁** pthread_mutex_destroy()函数用于销毁互斥锁,pthread_mutex_destroy()函数原型如下: ```c #include
int pthread_mutex_destroy(pthread_mutex_t *mutex); ``` pthread_mutex_destroy()函数参数:mutex指向目标互斥锁。 pthread_mutex_destroy()函数返回值:调用成功时返回0;失败将返回一个非0值的错误码。 被pthread_mutex_destroy()销毁之后的互斥锁,就不能再对它进行上锁和解锁了,需要再次调用pthread_mutex_init()对互斥锁进行初始化之后才能使用。 线程同步-互斥锁示例: ```c #include
#include
#include
#include
#include
static pthread_mutex_t mutex; static int g_count = 0; static void *new_thread_start(void *arg) { int loops = 1000000; int l_count; printf("新线程start\n"); pthread_mutex_lock(&mutex); //互斥锁上锁 for(int i=0; i