Linux系统中的常用的系统函数

介绍系统编程中线程的概念以及线程的特点。

文件与文件夹

下面几个函数主要是获取文件夹的属性。

1
2
3
4
5
6
7
8
9
#include <sys/stat.h>

int stat(const char* path,struct stat* buf);//获取文件或目录的状态信息,path是文件或目录的路径,buf是一个结构体,用来保存文件或目录的状态信息。返回0表示成功,返回-1表示失败。

int fstat(int fd,struct stat* buf);//获取文件描述符fd的文件状态信息,buf是一个结构体,用来保存文件描述符fd的文件状态信息。返回0表示成功,返回-1表示失败。

int lstat(const char* path,struct stat* buf);//获取符号链接文件的文件状态信息,返回该符号链接的消息,而不是它指向的文件的消息。path是符号链接文件的路径,buf是一个结构体,用来保存符号链接文件的文件状态信息。返回0表示成功,返回-1表示失败。

int fstatat(int dirfd,const char* path,struct stat* buf,int flags);//获取文件或目录的状态信息,dirfd是文件描述符,path是文件或目录的路径,buf是一个结构体,用来保存文件或目录的状态信息,flags是附加选项。返回0表示成功,返回-1表示失败。

上面四个函数的返回值都是0表示成功,返回-1表示失败。

系统IO

高级IO

进程环境

首先我们需要知道退出函数:

1
2
3
4
5
6
#incldue <stdlib.h>
void exit(int status);
void _Exit(int status);

#include <unistd.h>
void _exit(int status);//这个函数不会进行清理操作

这里需要注意的是 exit() 函数是退出进程,return 语句是返回函数 pthread_exit() 是退出线程。所以我们需要注意的是,需要选择正确的函数

上述的三个函数使用了两种头文件,这是因为它们不是同一个标准总说明的。

1
2
3
4
#include <stdlib.h>

int atexit(void(*func)(void));//注册一个函数,在程序退出时调用该函数
//这个函数需要注意:1. 使用了这个函数就可以不再使用 exit() 函数了。2. 函数执行的顺序和函数声明的顺序相反.

下面主要是动态内存分配的函数

1
2
3
4
5
6
#include <stdlib.h>
void* malloc(size_t size);//分配size字节的内存,返回指向该内存的指针
void* calloc(size_t nmemb,size_t size);//分配nmemb个size字节的内存,并初始化为0
void* realloc(void* ptr,size_t size);//重新分配size字节的内存,并拷贝原来的数据到新内存中,返回指向新内存的指针
void* alloca(size_t size);//这个函数其实就是 malloc 不同就是这个函数在栈中分配内存,而 malloc 使用堆
void free(void* ptr);//释放ptr指向的内存

下面是关于环境变量的函数

1
2
3
4
5
6
7
8
#include <stdlib.h>

char* getenv(const char* name);//获取环境变量的值,返回值是指向字符串的指针,如果没有找到的话返回NULL

int putenv(char* string);//根据name=value的形式设置环境变量。如果 name 已经存在则先删除原来的定义。返回0表示成功,返回 非0 表示失败
int setenv(const char* name,const char* value,int replace);//设置环境变量的值,replace=非0 则会先删除原来的定义,replace=0则不删除现有定义(即使 name 不设置为新的 value)
int unsetenv(const char* name);//删除环境变量,即使 name 不存在也不会报错。返回0表示成功,返回-1表示失败
int clearenv(void);//清空环境变量,返回0表示成功,返回-1表示失败

下面是关于不同函数之间的跳转函数

1
2
3
4
#include <setjump.h>

int setjmp(jmp_buf env);//设置跳转点,返回0
void longjmp(jmp_buf env,int val);//跳转到env处,并返回val,这个 val 是用来标识从哪里跳转过来的标识位。

下面是获取进程的资源限制的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sys/recource.h>

int getrlimit(int resource,struct rlimit* rlim);//获取资源限制,返回0表示成功,返回 非零 表示失败
int setrlimit(int resource,const struct rlimit* rlim);//设置资源限制,返回0表示成功,返回 非零 表示失败


struct rlimit{
rlim_t rlim_cur;//当前限制,又可以称之为软限制,其必须小于硬限制
rlim_t rlim_max;//最大限制,又可以称之为硬限制,其必须大于软限制
}

//只有管理员才可以修改 硬链接
//任何一个进程都可以更改软限制(必须 >= 硬限制)
//任何一个进程都可以修改自己的硬限制,但是必须 >= 软限制


下面是获取系统中的限制,**注意不同的标准会有不同的限制名称(name)**,但是使用的函数都是一样的。

1
2
3
4
5
6
7
#include <unistd.h>

long sysconf(int name)//获取系统配置信息,name是系统配置选项,返回值是系统配置值,如果出错返回-1

long pathconf(const char* path,int name)//获取文件系统配置信息,path是文件路径,name是文件系统配置选项,返回值是文件系统配置值,如果出错返回-1

log fpathconf(int fd,int name)//获取文件描述符fd的配置信息,name是文件描述符配置选项,返回值是文件描述符配置值,如果出错返回-1

上面后两个函数主要是用来获取路径和文件的限制的,而第一个函数主要是通用的限制

进程属性

这里主要是对于获取进程中的属性的函数:

1
2
3
4
5
6
7
8
#include <sys/time.h>

struct tms{
clock_t tms_utime;//用户态cpu时间
clock_t tms_stime;//内核态cpu时间
clock_t tms_cutime;//所有子进程的用户态cpu时间
clock_t tms_cstime;//所有子进程的内核态cpu时间
}
1
2
3
4
5
6
7
8
#include <sys/times.h>

clock_t times(struct tms* buf)//任何进程都可以使用这个函数来获取它和它的子进程的用户 cpu 时间、系统 cpu 时间、墙上时钟。这个函数的返回值就是从系统启动到现在的墙上时间。参数buf是一个结构体,用来保存时间信息。如果出错则返回 -1。

//使用方法
struct tms tms_buf;
clock_t now = times(&tms_buf);
int time_s = now / sysconf(_SC_CLK_TCK); //将墙上时间转换为秒

需要注意,times函数返回的是墙上时间,这个时间的单位是 ticks 而不是秒,所以我们需要将其除以 sysconf(_SC_CLK_TCK) 来得到秒。

下面主要是获取进程的资源使用情况的函数:进程会计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <sys/acct.h>

struct acct {
char ac_flag; /* Flags. */
uint16_t ac_uid; /* Real user ID. */
uint16_t ac_gid; /* Real group ID. */
uint16_t ac_tty; /* Controlling terminal. */
uint32_t ac_btime; /* Beginning time. */
comp_t ac_utime; /* User time. */
comp_t ac_stime; /* System time. */
comp_t ac_etime; /* Elapsed time. */
comp_t ac_mem; /* Average memory usage. */
comp_t ac_io; /* Chars transferred. */
comp_t ac_rw; /* Blocks read or written. */
comp_t ac_minflt; /* Minor pagefaults. */
comp_t ac_majflt; /* Major pagefaults. */
comp_t ac_swaps; /* Number of swaps. */
uint32_t ac_exitcode; /* Process exitcode. */
char ac_comm[ACCT_COMM+1]; /* Command name. */
char ac_pad[10]; /* Padding bytes. */
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <sys/acct.h>

int acct(const char* filename)//该函数将进程的资源使用情况写入文件,filename是文件名,成功返回0,失败返回-1。在 Linux 中这个文件夹一般都是/var/log/ 文件夹下。

//使用方法
int main() {
// 启用进程会计
if (acct("/var/log/process_accounting")) {
perror("acct");
return 1;
}

// 执行一些工作
printf("Doing some work...\n");
sleep(5); // 模拟工作

// 禁用进程会计
if (acct(NULL)) {
perror("acct");
return 1;
}

return 0;
}

会计记录对应于进程而不是程序。在 fork 之后,内核为子进程初始化一个记录,而不是在一个新程序被执行时初始化。如果一个进程顺序执行了 3 个程序(AexecB、BexecC、最后是Cexit),则只会写一个会计记录,并且该记录中的命令名对应于程序 C,但是 CPU 时间是 A、B、C进程之间之和

进程调度

主要是函数设置“友好度”,也就是优先级:

1
2
3
4
5
6
7
8
#include <unistd.h>

int nice(int inc)//设置进程的“友好度”,inc是增加的“友好度”,返回新的友好值表示成功,返回-1表示失败。友好值越小则优先级越高。

int getpriority(int which,int who)//获取进程的优先级,which是进程类别,who是进程ID,返回优先级,返回-1表示失败。参数 which 可以设置为 PRIO_PROCESS,表示进程优先级,PRIO_PGRP 表示进程组优先级,PRIO_USER 表示用户优先级。参数是用来控制 who 如何解释的。

int setpriority(int which,int who,int prio)//设置进程的优先级,which是进程类别,who是进程ID,prio是优先级,返回0表示成功,返回-1表示失败。前两个参数同 getpriority() 函数。参数 prio 是要设置的优先级。

第14章 文件系统及其挂载

1.

Linux的系统调度

Linux提供的调度器包括:完全公平调度器(CFS)、实时调度器(RT)、截止时间调度器(DL)

用户名、主机名相关函数

这是获取用户名的方法

1
2
3
4
5
uid_t userid;
struct passwd* pwd;
userid = getuid();
pwd = getpwuid(userid);
printf("%s",pwd->pw_name);

获取主机名的方法

1
int gethostname(char* buf,size_t size)//失败返回值相同

进程相关函数

  1. 退出函数:
    1
    2
    3
    4
    5
    int exit(int status)//该函数返回一个status,并且会刷新stdio缓冲区。这个函数的执行动作:首先执行通过函数atexit()或者是on_exit()注册的退出函数,然后刷新stdio缓冲区最后调用_exit()函数退出。                                                                               <stdlib.h>
    void _exit(int status)//该函数不会返回值,所以这个函数总是可以成功。实际上exit函数就是调用_exit() <unistd.h>

    int at_exit(void(*fun)(void))//这个函数的所注册的函数,没有参数并且无法将函数的状态返回。 <stdlib.h>
    int on_exit()//这个函数规避上面哪个函数的缺点,可以返回值,可以传递参数

下面是获取进程相关信息的函数:

首先我们需要知道实际、有效之间的关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <unistd.h>

pid_t getpid(void)//获取当前进程的进程号
pid_t getppid(void)//获取父进程的进程号
uid_t getuid(void)//获取当前进程的用户ID
uid_t geteuid(void)//获取当前进程的有效用户ID
gid_t getgid(void)//获取当前进程的组ID
gid_t getegid(void)//获取当前进程的有效组ID


uid_t getgroups(int size,gid_t list[])//获取当前进程的组列表,size是数组的大小,list是gid_t数组,返回实际的组数
int setuid(uid_t uid)//设置当前进程的用户ID,成功返回0,失败返回-1
int setgid(gid_t gid)//设置当前进程的组ID,成功返回0,失败返回-1
int seteuid(uid_t euid)//设置当前进程的有效用户ID,成功返回0,失败返回-1
int setegid(gid_t egid)//设置当前进程的有效组ID,成功返回0,失败返回-1

需要注意上面这些函数都没有失败返回

下面是进程等待函数(处理函数);

1
2
3
4
5
6
7
8
9
10
11
12
#include <sys/wait.h>

pid_t wait(int* status)//等待子进程结束,如果子进程结束,则返回子进程的进程号,如果没有子进程结束,则返回-1,status是子进程的终止状态。

pid_t waitpid(pid_t pid,int* status,int options)//等待子进程结束,如果子进程结束,则返回子进程的进程号,如果没有子进程结束,则返回-1,status是子进程的终止状态。options是选项,可以设置WNOHANG,表示如果没有子进程结束,则返回0,否则返回-1。

int waitid(idtype_t idtype,id_t id,siginfo_t* infop,int options)//等待子进程结束,如果子进程结束,则返回0,否则返回-1,infop是子进程的终止状态。options是选项,可以设置WNOHANG,表示如果没有子进程结束,则返回0,否则返回-1。

int wait3(int* status,int options,struct rusage* rusage)//等待子进程结束,如果子进程结束,则返回子进程的进程号,如果没有子进程结束,则返回-1,status是子进程的终止状态,rusage是资源使用情况。options是选项,可以设置WNOHANG,表示如果没有子进程结束,则返回0,否则返回-1。

int wait4(pid_t pid,int* status,int options,struct rusage* rusage)//等待子进程结束,如果子进程结束,则返回子进程的进程号,如果没有子进程结束,则返回-1,status是子进程的终止状态,rusage是资源使用情况。options是选项,可以设置WNOHANG,表示如果没有子进程结束,则返回0,否则返回-1。

错误处理函数

  1. 获取gnulibc处理函数:
    1
    const char* gnu_get_libc_version(void);//该函数返回版本字符串
  2. 三个错误处理函数:
    1
    2
    3
    void perror(const char* str)//该函数将str输出然后输出与当前error号相对应的信息
    char* strerr(int error)//该函数输出对应的字符串

移植性问题

  1. 关于宏的定义:
    1
    2
    3
    4
    5
    6
    7
    8
    _POSIX_SOURCE //一经定义(任何值),头文件会显露符合 POSIX.1-1990 和 ISO C(1990)标准的定义。该宏已被_POSIX_C_SOURCE 取代。
    _POSIX_C_SOURCE //
    _XOPEN_SOURCE //
    _BSD_SOURCE //
    _SVID_SOURCE //定义该宏会符合system V的定义
    _GNU_SOURCE //该宏一旦定义则符合上面所有


通用文件IO函数

访问模式:flag 以O_开头 访问权限:mode 以S_开头

  1. 基本文件操作函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    几个在文件函数中常用的参数:
    O_RDONLY
    O_WRONLY
    O_RDWR//读写
    O_CREAT//如果文件不存在则创建该文件
    O_EXCL//如果文件已经存在并且不是自己创建的返回-1
    O_ARREND//总是在文件的末尾添加数据
    int open (const char* pathname,int flag,...)//该函数打开一个文件 <sys/stat.h> <fcntl.h>
    int openat(int fd, )
    int creat(const char* pathname,int mode)// <fcntl.h>
    int read(int fd,void* buffer,size_t count)// <unistd.h>
    int write(int fd,void* buffer,size_t mode)//这个函数所显示的会比使用printf()函数输出数据要先,原因是该函数直接将数据发送至内核的缓存,但是printf()先发送到用户缓冲区。所以要慢一点。和缓冲方式有关 <unistd.h>
    int close(intfd)// <unistd.h>
    off_t lseek(int fd,off_t off,iny whereis)// <unistd.h> SEEK_SET SEEK_CUR SEEK_END
  2. 扩展文件操作函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    ssize_t pread(int fd,char*buf,size_t count,off_t offest)//按照指定的off进行读取,不会改变文件指针的位置         <unistd.h>
    ssize_t pwrite(int fd,char*buf,size_t count,off_t offest)//同上 <unistd.h>
    ssize_t readv(int fd,const struct* iov ,int iovcnt)//分散输入 <sys/uio.h>
    ssize_t writev(int fd,const struct* iov,int ioncnt)//集中输出 <unistd.h>

    readv()writev()两个函数需要配合struct iovec{void* iov_base;size_t iov_len}第一个是需要读取数据的首地址,第二个是读取的长度

    以上二者函数的功能相结合:
    <sys/uio.h>

    preadv()
    pwritev()


  3. 大文件(2GB以上)操作:

    1
    2
    3
    4
    #define _FILE_OOFEST_BITS 64
    off64_t
    open64()
    lseek64()
  4. 创建临时文件:

    1
    2
    int mkstemp(char*tempfile)//tempfile最后六个字母必须是XXXXXX,  char*tempfile = "/tmp/123XXXXXX" 返回fd
    FILE* tmpfile(void)//创建一个临时文件,文件流关闭后自动关闭,返回创建的临时文件的指针。
  5. 链接函数:

    1
    2
    3
    4
    int link(char* oldfilename,char*newfilename)//将new文件指向old文件,成功返回0,失败返回-1                               <unistd.h>
    int unlink(char* pathnae)//删除硬链接,如果是最后一个文件则会删除该文件。该函数无法移除目录,可以使用该函数删除文件。删除一个软链接也可使用 <unistd.h>
    int symlink(const char* filepath,const char* linkpath)//为filepath创建一个符号链接为linkpath <unistd.h>
    int readlink(const char* filepath,const char* linkpath,int bufsize)//读取符号链接文件中的字符串,将其存入buffer,buffer长度为<unistd.h>
  6. 文件/夹相关:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int rename(const char* oldfilepath,const char* newfilepath)//重命名函数                                     <unistd.h>
    int remove(const char* filepath)//如果是一个文件则会调用unlink()如果是一个文件夹则会调用redir(),和unlink()redir()一样其不会解引用符号链接。<stdio.h>
    int mkdir(const char* filepath,mode_t mode)//创建一个文件夹 <sys/stat.h>
    int rmdir(const char* filepath,mode_t mode)//移除文件夹dir <sys/stat.h>

    DIR* opendir(const char* dirpath)//打开对应的文件夹 <dirent.h>
    void rewindir(DIR* dirname)//将该文件夹的读取指针重置为开始处
    int closedir(DIR* dirname)//关闭文件夹
    int nftw(const char* filepath,int (*func)(const char* pathfile,const struct stat* statbuf,int tepyflage,struct FTW*futbuf),int nopenfd,int flags)//打开文件夹然后执行定义的函数,其中nopenfd是可以打开的最大文件数

    char* getcwd(char*buf,size_t size)//将当前进程的工作目录存放在buf中,长度是size,大于size返回NULL,设置errno:ERANGE
    int chdir(const char* pathfile)//改变当前的目录
    int fchdir(int fd)//和上面一样只是接受fd,而不是路径。
  7. 文件夹相关:

    1

通用IO模型之外的函数

1
2
3
int ioctl(int fd,int request,...)//                                  <unistd.h>
int fcntl(int fd,int cmd,...)//该函数主要用来修改文件的文件权限和文件模式 <fcntl.h>

复制文件描述符

实际上就是shell中的重定向操作

1
2
3
int dup(int oldfd)//该函数返回一个新的未使用的最小的文件描述符,该类函数复制oldfd,然后更改名为newfd。二者都指向old         <unistd.h>
int dup2(int oldfd,int newfd)//将oldfd重定义至newfd
int dup3(int oldfd,int newfd,int flg)//和dup3一样只是多了一个flg

上面三个函数,成功返回新的文件描述符,失败返回-1

高级IO

IO多路转接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <sys/signal.h>

int FD_ISSET(int fd, fd_set *fdset)//该函数判断fd是否在fdset中,返回1表示在,0表示不在。
void FD_CLR(int fd, fd_set *fdset)//该函数从fdset中删除fd
void FD_SET(int fd, fd_set *fdset)//该函数将fd加入fdset
void FD_ZERO(fd_set *fdset)//该函数将fdset清空

//上面四个函数的使用方法:
fd_set rset;
int fd;
FD_ZERO(&rset);
FD_SET(fd, &rset);
FD_SET(STDIN_FILENO, &rset);//这里是将标准输入加入到rset中

int select(int maxfdpl, fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout)//该函数可以监视多个文件描述符,并等待它们中的一个或多个准备就绪。
//第一个参数的含义是,最大的文件描述符 + 1
//这个函数的中间三个参数都可以是 NULL ,表示不监视相应的事件。
//最后一个参数 timeout 指定阻塞的时间,这个参数具有三种情况:
// timeout == NULL 此时表示永远等待
// timeout->tv_sec == 0 && timeout->tv_usec == 0 此时表示永远不等待
// timeout->tv_sec != 0 && timeout->tv_usec != 0 此时表示等待指定的时间
//函数的返回值也有三种情况
// -1 表示出错
// 0 表示超时
// 正常返回:准备就绪的 fd

int pselect(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,const struct timespec* timeout,const sigset_t* sigmask)//该函数和select()类似,但是可以指定信号掩码,只有在信号掩码中指定的信号才会被捕获。

int poll(struct pollfd* fds,nfds_t nfds,int timeout)//该函数和select()类似,但是可以指定超时时间。

int epoll_create(int size)//创建一个epoll句柄,size是最大的监听数量。

信号

  1. 信号的发送

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    kill(1234,9)//函数kill(pid_t,int)这个函数可以发送信号给对应的进程,杀死1234进程
    raise(int)//该函数和自己通信,发送信号给自己。
    alarm(time)//和自己通信,time时间到了之后执行相应的信号,默认是9号(杀死),这个函数其实是一个定时器函数,其参数就是需要定时的时间,如果到达了指定的时间,则会产生信号 SIGALRM 如果不管这个信号,则默认的操作就是杀死调用者。
    //每一个进程只可以有一个 armal 时钟。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>

    int main(int argc, const char *argv[])
    {
    printf("%d\n",alarm(7)); //7 秒定时器,此时返回 0 ,前面没有使用定时器
    sleep(2); //睡 2 秒
    while(1)
    {
    printf("%d\n",alarm(3)); //重新设置定时器值为 3 ,返回上次定时器剩余的时间,7-2 = 5
    sleep(7); //睡7秒,后会结束进程
    }
    return 0;
    }
  2. 信号的接收

    1
    int pause(void)该函数会中断进程,然后等待一个信号的到来,在执行该信号。该函数只会返回-1
  3. 信号的处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    signal(SIG_INT,SIG_IGN)//第一个参数是信号,第二个参数是处理方式:无视、默认、或者是程序员设置的函数。

    SIG_IGN无视 SIG_DFL默认 第三种选择就是使用指定的函数来处理信号。

    #include <signal.h>

    void (*signal(int signum, void (*handler)(int)))(int);

    参数
    signum:一个整数,表示信号的编号。
    handler:一个指向信号处理函数的指针,或者一个特殊的值 SIG_IGN(忽略信号)或 SIG_DFL(使用默认处理程序)。

    返回值
    如果 signal() 调用成功,它将返回先前注册的信号处理函数的指针。
    如果调用失败,返回值将是 SIG_ERR。

上面这个函数的局限性在于,不改变信号的处理方式就无法知道信号的现在的处理方式。所以,如果想知道信号的现在的处理方式,需要使用sigaction函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <signal.h>

int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact);
//第一个参数是信号,第二个参数是指向结构体的指针,结构体中包含信号处理函数和信号属性。第三个参数是指向旧的信号处理函数的指针,可以不用。
//这个函数可以设置信号的处理方式,包括忽略、默认、或者是程序员设置的函数。
//一般使用这个函数来替代函数 signal() ,因为 signal() 函数的局限性在于不知道信号的现在的处理方式。
struct sigaction {
void (*sa_handler)(int); //信号处理函数的指针
sigset_t sa_mask; //信号掩码
int sa_flags; //信号属性
void (*sa_sigaction)(int, siginfo_t *info, void *); //信号处理函数的指针, 第二个参数可以获取额外的消息
};

struct siginfo {
int si_signo; //信号编号
int si_errno; //出错信息
int si_code; //信号来源
pid_t si_pid; //发送信号的进程ID
uid_t si_uid; //发送信号的用户ID
int si_status; //信号返回值
void *si_addr; //信号发生地址
union sigval si_value; //Union提供的额外消息
};
union sigval {
int sival_int; //整数值
void *sival_ptr; //指针值
};

//注意,只有在 sa_flags 中设置了 SA_SIGINFO 标志位,才可以使用 sa_sigaction 成员。并且此时信号处理函数原型变成了:
//void (*sa_sigaction)(int, siginfo_t *info, void *);




int sigprocmask(int how,const sigset_t* set,sigset_t* oldset)//设置或者获取进程的信号掩码,how表示设置或者获取方式,set是新的信号掩码,oldset是旧的信号掩码。成功返回0,失败返回-1。

int sigpending(sigset_t* set)//获取进程的未决信号集,成功返回0,失败返回-1。

int sigsuspend(const sigset_t* mask)//挂起进程,直到收到一个信号,或者进程接收到一个信号。成功返回0,失败返回-1。

int sigwait(const sigset_t* set,int* sig)//等待一个信号,如果有信号到来,则将其存入sig中,成功返回0,失败返回-1。

int sigwaitinfo(const sigset_t* set,siginfo_t* info)//等待一个信号,如果有信号到来,则将其信息存入info中,成功返回0,失败返回-1。

  1. 信号分为标准信号和实时信号。信号产生的原因:1. 硬件产生的异常,如/0,访问了不可以访问的内存等等;2. 发生了软件定义的事件;3. 用户按下了具有信号作用的终端特殊字符·

对于信号集操作的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <signal.h>

int sigemptyset(sigset_t* set)//初始化信号集,将其设置为0
int sigfillset(sigset_t* set)//初始化信号集,将其设置为所有信号
int sigaddset(sigset_t* set,int signum)//向信号集中添加一个信号
int sigdelset(sigset_t* set,int signum)//从信号集中删除一个信号
//上面四个函数成功返回 0 失败返回 -1

int sigismember(const sigset_t* set,int signum)//判断信号是否在信号集中
//这个函数是成功返回 1 失败返回 0


int sigprocmask(int how, const sigset_t* restrict set, sigset_t* restrict oldset)//设置或者获取进程的信号掩码,how表示设置或者获取方式,set是新的信号掩码,oldset是旧的信号掩码。成功返回0,失败返回-1。
//上面这个函数的 how 参数可选三个值:

//SIG_BLOCK:将set中的信号加入进程的信号掩码中,同原来的sigset_t中取并集
//SIG_UNBLOCK:将set中的信号从进程的信号掩码中删除,同原来的 sigset_t 中取交集
//SIG_SETMASK:将set中的信号设置为进程的信号掩码


int sigpending(sigset_t* set)//获取进程的未决信号集,成功返回0,失败返回-1。

int sigsuspend(const sigset_t* mask)//挂起进程,直到收到一个信号,或者进程接收到一个信号。成功返回0,失败返回-1。虽然这个函数可以使用sigprocmask() + pause()来实现,但是sigsuspend()函数提供了更加安全的接口。
//这个函数在返回之后会将原来的信号掩码恢复。

信号中函数跳转

这两个函数的使用常见主要是在信号处理函数中,用于跳转到其他函数中,主要是 main()

1
2
3
4
5
#include <signal.h>

int sigsetjmp(sigjmp_buf env,int savemask)//设置信号跳转环境,env是跳转环境,savemask是保存的信号掩码,成功返回0,失败返回非零。需要确定的是如果 savemask == !0 ,则表示在 env 中保存进程当前的 mask 值,否则表示不保存。

void siglongjmp(sigjmp_buf env,int val)//跳转至信号跳转环境,env是跳转环境,val是返回值。

信号的发送

1
2
3
4
5
6
7
8
9
10
11
12
#include <signal.h>

int kill(pid_t pid,int sig)//发送信号给进程pid,信号是sig,成功返回0,失败返回-1。这个函数将信号发送给进程或者是进程组
//这个函数是按照 pid 的大小关系来判断是给进程还是进程组发送信号的。
//pid == 0 将信号发送给与调用进程同组的所有进程
//pid > 0 将信号发送给进程 pid
//pid < 0 将信号发送给进程组 -pid
//pid == -1 将信号发送给调用者所有拥有权限的进程
int reise(int signo)//这个函数用来给自己发送信号

#include <stdlib.h>
void abort(void)//该函数用来终止程序,会调用信号处理函数,然后终止程序。这个函数其实就是调用了函数 reise(SIGABRT)

上面两个函数成功都返回 0 ,失败都是 -1

信号名和编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <signal.h>

void psignal(int sig, const char* message)//参数 message 是自定义的文本段,该函数根据 sig 输出 message: (message后面还有加上一个冒号和一个空格)然后接上这个信号的简介。这个函数和 perror()类似。message参数可以是 NULL,此时表示不会出自定义的文本段

//使用方法:
psignal(SIGINT,"Signal SIGINT is");//输出:Signal SIGINT is: Interrupted

void psiginfo(const siginfo_t* info, const char* message)//打印信号的名字和信息,message是可选的。


int sig2str(int signo, char *str)//通过信号的编码转变称为字符串

int str2sig(const char *str, int *signop)//通过字符串转变为信号的编码

//上面这两个函数的返回值是0表示成功,-1表示失败。
#include <string.h>

char *strsignal(int signo)//返回信号的名字,如果没有对应的名字则返回空指针。


共享内存

  1. 创建共享内存:
    1
    2
    3
    int shmget(key_t key,size_t size,int shmflg)//第一个参数是IPC_PRIVATE或者是函数ftok的返回值,第二个参数是共享内存的大小,第三个参数是权限位(0777)以八进制开头  成功返回共享内存的标识符,失败返回-1

    ftok(const char* pathname,int proj_id)//这个函数的作用是获取一个key,将路径名和项目标识符转换为一个ipc通信密钥。项目id是随便取的,但是如果是pathname和pro_j都相同的话对应的key也是相同的。
  2. 映射共享内存地址至用户空间
    1
    2
    3
    4
    5
    void* shmat(int shmid,const void*shmaddr,int shmfig)//这个函数将位于内核空间的地址映射到用户空间。第一个参数是共享内存的shmid,第二个参数是映射到哪里(地址)一般是NULL,让系统帮我们决定,权限位一般是0表示可读可写,或者是SHM_RDONLY表示只读

    int shmdt(const void shmaddr)//删除位于用户空间中的共享内存,不会删除位于内核空间中的内存。成功返回0,失败返回-1。

    int shmctl(const void shmaddr)//删除位于内核地址空间中的共享内存。

消息队列

  1. 创建消息队列
    1
    int msgget(key_t key,int msgflg)//第一个参数和前面所说的shmget函数中的key一样,可以使用ftok函数获得一个key或者是使用IPC_PRITAVE宏获取一个key。第二个参数就是权限位成功后返回消息队列的序号,失败返回-1。注意这个函数和创建共享内存的函数shmget()函数的区别是没有指定大小,是因为这是一个队列可以一直往里面输送数据。所以可以不用大小。
  2. 删除消息队列
    1
    int msgctl(int msgid,int cmd,struct msqid_ds* buf);//这个函数是删除消息队列,第一个参数是消息队列的id,第二个参数是类似于命令行:IPC_STAT表示读取消息队列的属性,存储在后面的buf中,IPC_SET:设置消息队列的属性,从buf中取出。IPC_RMID:删除消息队列,此时后面一个参数为NULL。成功返回函数0,失败返回-1。

杂项

  1. 无名管道:适用于有亲缘关系的进程,使用函数pipe()创建:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int fd[2];//用以存储管道两端的读写文件,读文件区的文件描述符是fd[0],写的是fd[1]
    pipe(fd);

    write(fd[1],"nihao",5);//向文件fd[1]中写nihao

    char buffer[100];
    read(fd[0],buffer,32);//向文件fd[0]中读取32位字节,存入buffer中

    //注意如果要在子进程和父进程之间使用调用pipe函数要在fork函数之前使用。
  2. 有名管道:可以使用于没有关系的进程。使用函数mkfifo()函数创建,注意该函数只是创建一个文件,这相当于无名管道中的函数pipe()所创建的文件。有名管道使用该文件进行没有关系之间的进程的交流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int ret;
ret = mkfifo("filename",umake)//umake参数的含义是权限
if(ret == 0){
perror("mkfifo funtion error");
exit(1);
}
if(access("filename",F_OK) == -1){
fd = open("filename",O_WRONLY);
if(fd == -1){
perror("open funtion error ");
exit(1);
}

}

write(fd,"nihao",5);
  1. 注意不管是无名管道还是又名管道,在读取没有内容的文件时都会阻塞。
  1. 选项处理函数getopt()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int getopt(int agrc,char*const*agrv,char* optstring)//这个函数的前两个参数就是main函数的参数,第三个参数是需要处理的选项的字符串"::a:b:c",第一个:表示不打印错误消息,第二个:表示a有参数,如果没有接入参数会报错。

    返回值是:到底了返回-1,出问题了也是-1

    这个函数还包括四个不在函数参数列表中的变量:
    1. extern int opterr:这个参数表示设置为1打印错误消息,设置为0不打印错误消息
    2. extern int optind:这个表示指向下一个argv中未处理的选项
    3. extern int optopt:这个哪一个选项出了问题
    4. extern char* optarg:这个是表示选项所带的参数

中断函数可以传参吗

中断处理函数不能有返回值和形式参数,因为中断处理函数是由硬件(或触发器)调用的,没有程序向其传递参数,也没有程序接收其返回值,其参数通过全局变量传递。

但是,请注意,如果要检测的其他函数的全局变量值在中断服务函数中发生更改,则volatile关键字将用于定义全局变量。因为主程序可能会将变量读入寄存器,然后每次只使用寄存器中的变量副本。如果此时不使用volatile关键字,则在中断服务函数中修改变量的操作将被短路。

线程

线程的基本操作函数:创建、删除、标识

在线程中有三种方式可以退出线程:

  1. 线程函数返回,return语句
  2. 调用pthread_exit()函数,退出函数
  3. 调用pthread_cancel()函数,该函数可以请求取消同一个进程中的其他线程。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <pthread.h>

    int pthread_create(pthread_t* thread,const pthread_attr_t* attr,void*(*start_routine)(void*),void* arg)
    //创建线程,第一个参数是线程的id,第二个参数是线程属性,第三个参数是线程的入口函数,第四个参数是入口函数的参数。当然,第二个参数可以设置为NULL,表示使用默认的线程属性。
    //成功返回 0 ,失败返回错误码。

    int pthread_exit(void *retval)//退出线程,第一个参数是线程的返回值。如果对于线程的返回值不感兴趣,则可以传入 NULL。

    int pthread_join(pthread_t thread,void**retval)//等待线程结束,第一个参数是线程的id,第二个参数是返回值,如果线程没有结束,则一直阻塞。

    pthread_t pthread_self(void)//返回当前线程的id

    int pthread_equal(pthread_t t1,pthread_t t2)//判断两个线程是否相同,如果相同返回1,否则返回0。

    int pthread_cancel(pthread_t thread)//用来请求取消同一个进程中的其他线程。

    int pthread_detach(pthread_t thread)
    //分离线程,将线程从父进程中分离,使得线程成为孤儿进程,不会需要等待线程结束。

    void pthread_cleanup_push(void (*rtn)(void*),void* arg)//注册线程退出函数,第一个参数是线程退出函数,第二个参数是线程退出函数的参数。

    void pthread_cleanup_pop(int execute)//如果 execute 为0,则将最近注册的线程退出函数弹出(不会执行),如果 execute 为1,则将执行最近注册的线程退出函数。

使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>

pthread_t ntid;

void* test_pthread(void* t)
{
pthread_t tid = pthread_self();

printf("son thread tid is :%ld\n",tid);

printf("son pid is :%d\n",getpid());
return (void*) 0;
}

void print_pid_tid (void)
{
printf("father pid is :%d\n",getpid());
printf("father tid is :%ld\n",pthread_self());
printf("\n\n");
}

int main (void)
{
pthread_create(&ntid,NULL,test_pthread,NULL);
print_pid_tid();
sleep(2);//这里需要主线程等等,否组可能会直接退出运行return语句导致新线程没有机会运行。
return 0;
}

线程的同步函数:互斥锁、条件变量、读写锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <pthread.h>
#include <time.h> //for pthread_mutex_timedlock(),各个名字中有 time 字样的函数

int pthread_mutex_init(pthread_mutex_t*restrict mutex,const pthread_mutexattr_t* restrict attr)//初始化互斥锁,第一个参数是互斥锁的地址,第二个参数是互斥锁的属性。

int pthread_mutex_destroy(pthread_mutex_t* mutex)//销毁互斥锁

int pthread_mutex_lock(pthread_mutex_t* mutex)//互斥锁,锁定互斥锁,如果互斥锁已经被其他线程锁定,则该线程会一直阻塞,直到互斥锁被解锁。

int pthread_mutex_unlock(pthread_mutex_t* mutex)//解锁互斥锁

int pthread_mutex_trylock(pthread_mutex_t* mutex)//尝试锁定互斥锁,如果互斥锁已经被其他线程锁定,则该函数会立即返回错误。返回 EBUSY 表示互斥锁已经被其他线程锁定。

int pthread_mutex_timedlock(pthread_mutex_t* mutex,const struct timespec* restrict tsptr)//指定阻塞等待的时间,如果超过了这个时间则返回 ETIMEDOUT。就是加上了超时机制的 pthread_mutex_lock() 函数。这个时间是使用结构体 timespec 来表示的,其实用秒和纳秒来描述时间。

/******************条件变量******************/
int pthread_cond_init(pthread_cond_t*restrict cond,const pthread_condattr_t* restrict attr)//初始化条件变量,第一个参数是条件变量的地址,第二个参数是条件变量的属性。

int pthread_cond_destroy(pthread_cond_t*restrict cond)//销毁条件变量

int pthread_cond_wait(pthread_cond_t*restrict cond,pthread_mutex_t*restrict mutex)//条件变量,等待条件变量,如果条件变量没有被唤醒,则该线程会一直阻塞,直到条件变量被唤醒。

int pthread_cond_signal(pthread_cond_t*restrict cond)//唤醒一个等待条件变量的线程

int pthread_cond_broadcast(pthread_cond_t*restrict cond)//唤醒所有等待条件变量的线程

int pthread_cond_timedwait(pthread_cond_t*restrict cond,pthread_mutex_t*restrict mutex,const struct timespec* restrict tsptr)//指定阻塞等待的时间,如果超过了这个时间则返回 ETIMEDOUT。就是加上了超时机制的 pthread_cond_wait() 函数。这个时间是使用结构体 timespec 来表示的,其实用秒和纳秒来描述时间。

/******************读写锁******************/
int pthread_rwlock_init(pthread_rwlock_t* rwlock,const pthread_rwlockattr_t*restrict attr)//初始化读写锁,第一个参数是读写锁的地址,第二个参数是读写锁的属性。

int pthread_rwlock_destroy(pthread_rwlock_t* rwlock)//销毁读写锁

int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock)//读锁,获取读锁,如果读锁已经被其他线程锁定,则该线程会一直阻塞,直到读锁被解锁。

int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock)//写锁,获取写锁,如果写锁已经被其他线程锁定,则该线程会一直阻塞,直到写锁被解锁。

int pthread_rwlock_unlock(pthread_rwlock_t* rwlock)//解锁读写锁

int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock)//尝试获取读锁,如果读锁已经被其他线程锁定,则该函数会立即返回错误。

int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock)//尝试获取写锁,如果写锁已经被其他线程锁定,则该函数会立即返回错误。

int pthread_rwlock_timedrdlock(pthread_rwlock_t* rwlock,const struct timespec* restrict tsptr)//指定阻塞等待的时间,如果超过了这个时间则返回 ETIMEDOUT。就是加上了超时机制的 pthread_rwlock_rdlock() 函数。这个时间是使用结构体 timespec 来表示的,其实用秒和纳秒来描述时间。

int pthread_rwlock_timedwrlock(pthread_rwlock_t* rwlock,const struct timespec* restrict tsptr)//指定阻塞等待的时间,如果超过了这个时间则返回 ETIMEDOUT。就是加上了超时机制的 pthread_rwlock_wrlock() 函数。这个时间是使用结构体 timespec 来表示的,其实用秒和纳秒来描述时间。

上面所有的函数的返回值都是成功则返回 0 ,失败则返回错误编码

线程控制

主要是线程、同步方式的属性控制函数

线程控制函数:

结构体 pthread_attr_t 具有四个参数,分别是:

  1. detachstate:线程分离状态,默认值为 PTHREAD_CREATE_JOINABLE,表示线程是可分离的。
  2. guardsize:线程栈末尾的警戒缓冲区的大小(以字节为单位)
  3. stackaddr:线程栈的最低地址
  4. stacksize:线程栈的最小长度(以字节为单位)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <pthread.h>

int pthread_attr_init(pthread_attr_t* attr)//初始化线程属性,第一个参数是线程属性的地址。

int pthread_attr_destroy(pthread_attr_t* attr)//销毁线程属性

int pthread_attr_setdetachstate(pthread_attr_t* attr,int detachstate)//设置线程的分离状态,第一个参数是线程属性的地址,第二个参数是分离状态。

int pthread_attr_getdetachstate(const pthread_attr_t* attr,int* detachstate)//获取线程的分离状态,第一个参数是线程属性的地址,第二个参数是分离状态的地址。

int pthread_attr_getstack(const pthread_attr_t* attr,void** stackaddr,size_t* stacksize)//获取线程的栈地址和大小,第一个参数是线程属性的地址,第二个参数是栈地址的地址,第三个参数是栈大小的地址。

int pthread_attr_setstack(pthread_attr_t* attr,void* stackaddr,size_t stacksize)//设置线程的栈地址和大小,第一个参数是线程属性的地址,第二个参数是栈地址,第三个参数是栈大小。

int pthread_attr_getstacksize(const pthread_attr_t* attr,size_t* stacksize)//获取线程的栈大小,第一个参数是线程属性的地址,第二个参数是栈



上面这几个函数的返回值都是成功则返回值,失败则返回 -1 并设置 errno == EINVAL。同时,有些 name 会返回一个变量名(>= 0)或者是提示该值是不确定的。不确定的至通过返回 -1 但是不设置 error 来表示

线程同步方式的属性

我们知道线程同步的方式包括下了:

  • 互斥锁
  • 条件变量
  • 读写锁
  • 屏障
  • 信号
    下面是这些同步方式的属性控制函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <pthread.h>

int pthread_mutexattr_init(pthread_mutexattr_t*restrict attr)//初始化互斥锁属性,第一个参数是互斥锁属性的地址。

int pthread_mutexattr_destroy(pthread_mutexattr_t*restrict attr)//销毁互斥锁属性

int pthread_mutexattr_setpshared(pthread_mutexattr_t*restrict attr,int pshared)//设置互斥锁的共享属性,第一个参数是互斥锁属性的地址,第二个参数是共享属性。

int pthread_mutexattr_getpshared(const pthread_mutexattr_t*restrict attr,int* pshared)//获取互斥锁的共享属性,第一个参数是互斥锁属性的地址,第二个参数是共享属性的地址。

int pthread_mutexattr_settype(pthread_mutexattr_t*restrict attr,int type)//设置互斥锁的类型,第一个参数是互斥锁属性的地址,第二个参数是类型。

int pthread_mutexattr_gettype(const pthread_mutexattr_t*restrict attr,int* type)//获取互斥锁的类型,第一个参数是互斥锁属性的地址,第二个参数是类型的地址。

int pthread_mutex_consistent(pthread_mutex_t*restrict mutex)//将互斥锁标记为一致状态,第一个参数是互斥锁的地址。

## 时间函数

主要是获取当前时间和时间相关的函数。

在这之前我们需要先明白下面这张图:

![](https://picgo-ysc.oss-cn-shenzhen.aliyuncs.com/web/202408280011486.png)

从图上可以看出,在 Linux 中主要有两种时间:
1. 日历时间
2. 分解时间

time_t 是一个非负数的整数,用以表示从 19701100:00:00 UTC 到现在所经过的秒数。

struct tm 是一个结构体,用来表示分解时间。

```c
#include <time.h>
struct tm {
int tm_sec; /* 秒 这里的秒数是从 0 到 60,最后一秒是润秒 */
int tm_min; /* 分 */
int tm_hour; /* 时 */
int tm_mday; /* 月日 */
int tm_mon; /* 月 */
int tm_year; /* 年 */
int tm_wday; /* 星期 */
int tm_yday; /* 年日 */
int tm_isdst; /* 是否夏令时 */
};

我们还可以从图上看出,`timeval` `time_t` `timespec` 其实表示的是同一个东西,不过精准度不同。`timeval` 精确到纳秒,`time_t` 精确到秒,`timespec` 精确到微秒。所以这里面精确度最高的是 `timespec`。但是不常用。

其中还有两个函数需要注意:`ctime()` 和 `asctime()`。这两个函数可以直接将 `time_t` 转换为字符串,`asctime()` 还可以将 `struct tm` 转换为字符串。
```c
#include <time.h>

struct timespec {
time_t tv_sec; /* 秒 */
long tv_nsec; /* 纳秒 */
};

/**********/
#include <sys/time.h>

struct timeval {
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 微秒 */
};
/**********/
#inlcude <time.h>

clockid_t 这是一个算数类型,同时内核为制定了一些选项:

CLOCK_REALTIME: 真实时间时钟,表示从某个参考时间点(通常是系统启动)开始的流逝时间。
CLOCK_MONOTONIC: 单调递增时钟,表示从某个参考时间点开始的流逝时间,不受系统时间调整的影响。
CLOCK_PROCESS_CPUTIME_ID: 当前线程的 CPU 时间。
CLOCK_THREAD_CPUTIME_ID: 当前线程的 CPU 时间。
CLOCK_MONOTONIC_RAW: 与 CLOCK_MONOTONIC 类似,但不受 NTP 调整的影响。
CLOCK_BOOTTIME: 自系统启动以来的时间,但不包括挂起时间。
CLOCK_SGI_CYCLE: SGI 系统上的周期计数器。
CLOCK_TAI: 国际原子时(TAI)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <time.h>
#include <sys/time.h>

time_t time(time_t* t)//获取当前时间,如果 t 非空指针,则将当前时间存储在 t 指向的地址中,并返回当前时间的秒数。
//一般的使用方法是
time_t now = time(NULL);

/*************************/
char* ctime(const time_t* timep)//将 time_t 转换为字符串,第一个参数是 time_t 指针,第二个参数是字符串指针。已经被标记为过时,不建议使用。
//一般的使用方法是
char* now = ctime(&now);

/*************************/
char* asctime(const struct tm* timeptr)//将 struct tm 转换为字符串,第一个参数是 struct tm 指针,第二个参数是字符串指针。已经被标记为过时,不建议使用。
//一般的使用方法是
char* now = asctime(localtime(&now));

/*************************/
struct tm* localtime(const time_t* timep)//将 time_t 转换为 struct tm,第一个参数是 time_t 指针,第二个参数是 struct tm 指针。
//一般的使用方法是
struct tm* now = localtime(&now);

/*************************/
struct tm* gmtime(const time_t* timep)//将 time_t 转换为 UTC 时间的 struct tm,第一个参数是 time_t 指针,第二个参数是 struct tm 指针。
//一般的使用方法是
struct tm* utcnow = gmtime(&now);

/*************************/
int gettimeofday(struct timeval* tv,void*restrict tz)//这个函数以距1970年1月1日00:00:00 的秒数的方式将当前时间存放在 tv 指向的 timeval 结构体中,第一个参数是 timeval 指针,第二个参数是 timezone 指针,这个参数必须是 NULL
//一般的使用方法是
struct timeval now;
gettimeofday(&now, NULL);

/*************************/
int settimeofday(const struct timeval* tv,const struct timezone* tz)//设置系统时间,第一个参数是 timeval 指针,第二个参数是 timezone 指针。
//一般的使用方法是
struct timeval now;
gettimeofday(&now, NULL);
struct timeval new_time;
new_time.tv_sec = 0;
new_time.tv_usec = 1000000; //这里表示精度
settimeofday(&new_time, NULL);

/*************************/
int clock_gettime(clockid_t clk_id,struct timespec* tp)//获取指定时钟的时间,第一个参数是时钟 id,第二个参数是 timespec 指针。第二个参数的作用是用以指定精度
//一般的使用方法是
struct timespec now;
timespec.tv_sec = 0;
timespec.tv_nsec = 1000000; //这里表示精度为 1 毫秒
clock_gettime(CLOCK_REALTIME, &now);

/*************************/
int clock_settime(clockid_t clk_id,const struct timespec* tp)//设置指定时钟的时间,第一个参数是时钟 id,第二个参数是 timespec 指针。
//一般的使用方法是
struct timespec new_time;
new_time.tv_sec = 0;
new_time.tv_nsec = 1000000; //这里表示精度为 1 毫秒
clock_settime(CLOCK_REALTIME, &new_time);

/*************************/
int clock_getres(clockid_t clk_id,struct timespec* res)//获取指定时钟的最小精度,第一个参数是时钟 id,第二个参数是 timespec 指针。
//一般的使用方法是
struct timespec res;
clock_getres(CLOCK_REALTIME, &res);
printf("The minimum resolution is: %ld.%09ld\n", res.tv_sec, res.tv_nsec);

/*************************/
size_t strftime(char*restrict s,size_t maxsize,const char*restrict format,const struct tm*restrict timeptr)//将 struct tm 按照 format 格式化为字符串,第一个参数是保存字符串的地址,第二个参数是字符串的最大长度,第三个参数是格式化字符串,第四个参数是 struct tm 指针。
//一般的使用方法是
char time_str[64];
struct tm* now = localtime(&now);
strftime(time_str, 64, "%Y-%m-%d %H:%M:%S", now);
printf("The time is: %s\n", time_str);
/*************************/

size_t strftime_l(char*restrict s,size_t maxsize,const char*restrict format,const struct tm*restrict timeptr,locale_t locale)//将 struct tm 按照 format 格式化为字符串,第一个参数是保存字符串的地址,第二个参数是字符串的最大长度,第三个参数是格式化字符串,第四个参数是 struct tm 指针,第五个参数是 locale。这个函数允许将区域指定为参数,其他和 `strftime()` 一样。
//一般的使用方法是
char time_str[64];
struct tm* now = localtime(&now);
locale_t mylocale = newlocale(LC_TIME_MASK, "zh_CN.UTF-8", NULL);
strftime_l(time_str, 64, "%Y-%m-%d %H:%M:%S", now, mylocale);
printf("The time is: %s\n", time_str);
/*************************/
char* strptime(const char* restrict s,const char* restrict format,struct tm*restrict tm)//将字符串按照 format 格式化为 struct tm,第一个参数是字符串地址,第二个参数是格式化字符串,第三个参数是 struct tm 指针。
//一般的使用方法是
char time_str[] = "2021-08-28 10:00:00";
struct tm time_tm;
strptime(time_str, "%Y-%m-%d %H:%M:%S", &time_tm);
printf("The year is: %d\n", time_tm.tm_year);
printf("The month is: %d\n", time_tm.tm_mon);


SUSv3 规定,调用 ctime()、gmtime()、localTime()或 asctime()中的任一函数,都可能会覆盖由其他函数返回,且经静态分配的数据结构。换言之,这些函数可以共享返回的字符数组和 tm 结构体,某些版本的 glibc 也正是这样实现的。如果有意在对这些函数的多次调用间维护返回的信息,那么必须将其保存在本地副本中。


time类函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <unistd.h>

unsigned int sleep(unsigned int seconds)//休眠指定秒数,第一个参数是休眠秒数。
//一般的使用方法是 sleep(10);
//返回 0 或者是没有休眠完的秒数

int nanosleep(const struct timespec* req,struct timespec* rem)//休眠指定时间,第一个参数是指定时间,第二个参数是剩余时间,函数会把剩余的时间存放在 rem 指向的结构体中。
//一般的使用方法是 struct timespec req = {10, 0}; nanosleep(&req, NULL);
//成功则返回 0 失败则返回 -1.

int usleep(useconds_t useconds)//休眠指定微秒数,第一个参数是休眠微秒数。
//一般的使用方法是
usleep(1000000);
//成功则返回 0 失败则返回 -1.

int clock_nanosleep(clockid_t clk_id,int flags,const struct timespec* req,struct timespec* rem)//休眠指定时间,第一个参数是时钟 id,第二个参数是标志位,第三个参数是指定时间,第四个参数是剩余时间。
//一般的使用方法是
struct timespec req = {10, 0};
clock_nanosleep(CLOCK_REALTIME, 0, &req, NULL);
//上面这个用法其实就相当于直接使用函数 nanosleep(req,rem);
//成功则返回 0 失败则返回 -1.

## 系统标识

```c
#include <sys/utsname.h>

struct utsname {
char sysname[]; /* 系统名称 */
char nodename[]; /* 节点名称 */
char release[]; /* 系统版本 */
char version[]; /* 系统版本 */
char machine[]; /* 硬件类型 */
};

int uname(struct utsname* buf)//获取系统信息,第一个参数是 utsname 指针。
//一般的使用方法是 struct utsname info; uname(&info);
//成功则返回 非负值 失败则返回 -1.
1
2
3
4
5
#include <unistd.h>

int gethostname(char* name,size_t len)//获取主机名,第一个参数是指定保存 name 的空间地址,第二个参数是指定 name 的长度。
//一般的使用方法是 char hostname[65]; gethostname(hostname, 64);
//成功则返回 0 失败则返回 -1.

网络套接字

消息的发送

1
#include <sys/socket.h>

Linux系统中的常用的系统函数
https://ysc2.github.io/ysc2.github.io/2024/08/27/Linux系统中的常用的系统函数/
作者
Ysc
发布于
2024年8月27日
许可协议