跳到主要内容

Linux Program Tips

·940 字·5 分钟

1. sysconfig() #

sysconfig() 是获取系统运行时配置信息的函数,包括内存、CPU 等。函数声明:

#include <unistd.h>
long sysconf(int name);

参数 name 用于指示要获取的信息,通过返回值将结果返回。新建一个 test.c ,获取当前系统的信息:

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

#define ONE_MB (1024 * 1024)

int main()
{
    printf("The number of processors configured is :%ld\n", sysconf(_SC_NPROCESSORS_CONF));
    printf("The number of processors currently online (available) is :%ld\n", sysconf(_SC_NPROCESSORS_ONLN));
    printf("The pagesize: %ld\n", sysconf(_SC_PAGESIZE));
    printf("The number of pages: %ld\n", sysconf(_SC_PHYS_PAGES));
    printf("The number of available pages: %ld\n", sysconf(_SC_AVPHYS_PAGES));
    printf("The memory size: %lld MB\n", (long long)sysconf(_SC_PAGESIZE) * (long long)sysconf(_SC_PHYS_PAGES) / ONE_MB );
    printf("The number of files max opened:: %ld\n", sysconf(_SC_OPEN_MAX));
    printf("The number of ticks per second: %ld\n", sysconf(_SC_CLK_TCK));
    printf("The max length of host name: %ld\n", sysconf(_SC_HOST_NAME_MAX));
    printf("The max length of login name: %ld\n", sysconf(_SC_LOGIN_NAME_MAX));
    return 0;
}

编译后执行:

$ gcc test.c
$ ./a.out
The number of processors configured is :1
The number of processors currently online (available) is :1
The pagesize: 4096
The number of pages: 221303
The number of available pages: 19370
The memory size: 864 MB
The number of files max opened:: 1024
The number of ticks per second: 100
The max length of host name: 64
The max length of login name: 256

2. attribute #

__attribute__ 是 GCC 提供的一种语法,可以帮助我们在编译时对声明的函数、变量和类型做一些特殊处理或者是检查操作,提升城程序的性能。语法格式为: __attribute__ ((attribute-list)) ,attribute-list 是指令集 , 分为三种类型:函数属性,变量属性,类型属性。__attribute__ 应该出现在函数、变量和类型声明的 “;” 前。

2.1. packed #

packed 用于设置变量或者结构体成员以最小的对齐方式对齐,减少空间浪费,例如:

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

struct foo1 {
    char a;
    int b[2];
};
struct foo2 {
    char a;
    int b[2];
} __attribute__((packed));

int main()
{
    int s1 = sizeof(struct foo1);
    int s2 = sizeof(struct foo2);
    printf("s1 = %d\ns2 = %d\n",s1,s2);

    return 0;
}

运行结果:

s1 = 12
s2 = 9

2.2. aligned(alignment) #

aligned 用于指定变量或者结构体成语按照 alignment 字节大小对齐。如果对齐长度有大于 alignment 的,就按照最大对齐长度对齐。例如:

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

struct foo1 {
    char a;
    int b[2];
};
struct foo2 {
    char a;
    int b[2] __attribute__((aligned(8)));
};
    int main()
    {
        int s1 = sizeof(struct foo1);
        int s2 = sizeof(struct foo2);
        printf("s1 = %d\ns2 = %d\n",s1,s2);

        return 0;
    }

运行结果:

s1 = 12
s2 = 16

2.3. constructor & destructor #

函数属性,设置 constructor 可以使函数在 main 方法之前执行,而设置 destructor 可以使函数在 main 方法之后执行。例如:

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

void __attribute__((constructor)) fun1()
{
    printf("function 1\n");
}
void __attribute__((destructor)) fun2()
{
    printf("function 2\n");
}

int main()
{
    printf("Main\n");

    return 0;
}

运行结果:

function 1
Main
function 2

2.4. visibility #

控制符号可见性,用与设置函数是否被导出,常用的有两个选项:

  1. __attribute__((visibility("default"))) ,用它定义的符号将被导出。
  2. __attribute__((visibility("hidden"))) ,用它定义的符号将不被导出,其他对象无法调用。

2.5 format #

format 用于检查格式化字符串与可变参数 ... 的匹配情况,防止出错。标准语法是:

format(archetype, string-index, first-to-check)
  • archetype 用于检查格式化字符串的类型,例如 printfscanf
  • string-index 表示传入函数的第几个参数是格式化字符串,从 1 开始数。
  • first-to-check 表示从传入函数的第几个参数开始检查,从 1 开始数。

例如:

#include <stdio.h>
#include <stdarg.h>

__attribute__((format(printf, 1, 2)))
void mylog(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    (void)printf(fmt, ap);

    va_end(ap);
}

int main(void)
{
    mylog("value = %d\n", 8);
    mylog("value = %d\n", "hello");

    return 0;
}

第二次调用 mylog() 时,因为传给格式化字符串的参数 "hello" 的类型不符,在编译会报错:

~$ gcc -Wall main.c -o main
main.c: In function 'main':
main.c:17:11: warning: format '%d' expects argument of type 'int', but argument 2 has type 'char *' [-Wformat=]
     mylog("value = %d\n", "hello");

3. size_t #

size_t 是 C/C++ 标准定义的数据类型,无符号,它在不同的系统中大小是不一样的,32 位系统中通常定义为 unsigned int ,4 Byte ,64 位系统中通常定义为 unsigned long ,8 Byte 。它的含义是当前系统可操作的内存最大值,通常用于内存相关的变量,可以确保不会因为类型太小导致变量溢出,提高程序的有效性和可移植性。例如:

void *malloc(size_t n)

如果把 n 定义为 unsigned int ,那么在 64 位系统中就有溢出的可能性。参考为什么 size_t 很重要

4. container_of #

container_of 是 Linux 内核定义的一个宏,在 linux/kernel.h 中声明,它的作用是通过结构体变量中某个成员的地址获得这个结构体变量的地址。定义:

#define container_of(ptr, type, member) ({ \
        const typeof( ((type *)0)->member ) *__mptr = (ptr); \
        (type *)( (char *)__mptr - offsetof(type,member) );})

三个参数分别表示:

  • ptr :结构体成员变量 member 的地址
  • type :结构体类型的名称
  • member :结构体中的成员变量的名称

宏定义包含两条语句:

const typeof( ((type *)0)->member ) *__mptr = (ptr); 

首先将 0 转化成 type 类型的指针变量(指向的地址为 0x0 ),然后再引用 member 成员 ((type *)0)->member ) )。注意这里的 typeof(x),是返回 x 的数据类型,那么 typeof( ((type *)0)->member ) 就是返回 member 成员的数据类型。那么这条语句整体就是定义了一个 member 成员的数据类型的指针 __mptr,指向 ptr ,而 ptr 就是 member 的地址。

(type *)( (char *)__mptr - offsetof(type,member) );

这条语句中的 offsetof 的作用是获取结构体中某个成员的偏移量,那么从 __mptr 指向的地址向前 offsetof 个字节就是 member 所属结构体变量的首地址。offsetof 的原型:

#define offsetof(type, member) (size_t)&( ((type *)0)->member )

type 表示结构体的名称,member 表示成员变量的名称。(type *)0 定义了一个 type 类型的指针,指向的地址是 0 ,那么成员 member 的地址就是它在结构体内的偏移量。

下面这个历程展示 container_of 和 offsetof 的用法:

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

#define offsetof(type, member) (size_t)&( ((type *)0)->member )

#define container_of(ptr, type, member) ({ \
        const typeof( ((type *)0)->member ) *__mptr = (ptr); \
        (type *)( (char *)__mptr - offsetof(type,member) );})

typedef struct foo
{
    char  a;
    int  b;
}foo;

int main()
{
    size_t off_set = 0;
    off_set = offsetof(foo, b);
    printf("foo->b offset: %lu\n",off_set);

    foo s ;
    printf("s address : %lu\n",(size_t)&s);
    s.b = 10;
    foo *p = container_of(&(s.b), foo, b);
    printf("p->b value : %d\n", p->b);
    printf("p address : %lu\n", (size_t)p);
    return 0;
}

运行结果:

foo->b offset: 4
s address : 140723509298320
p->b value : 10
p address : 140723509298320

5. PID 文件 #

在 Linux 系统的 /var/run/ 目录下可以看到很多 *.pid 文件,很多程序启动后都会在该目录下新建一个自己的 PID 文件,记录该进程的 PID ,方便管理进程。PID 文件的另一个作用是防止进程启动多个副本,进程启动后新建 PID 文件,然后为文件加独占的记录锁,只有获得 PID 文件写入权限(F_WRLCK)的进程才可以启动,并将自身的 PID 写入文件,锁定失败的进程启动退出。下面是一个例程:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <string.h>

int run = 1;

//锁定一个文件,参数是文件描述符,出错返回 -1    
int lockfile(int fd)
{
   struct flock lock;
   lock.l_type = F_WRLCK;  //独占性写锁
   lock.l_whence = SEEK_SET;  //为整个文件加锁
   lock.l_start = 0;
   lock.l_len = 0;
   return fcntl(fd, F_SETLK, &lock);
}

//新建 PID 文件并加锁,参数是 PID 文件的路径和当前进程的 PID ,失败返回负数
int creat_pidfile(const char *pid_file, pid_t pid)
{
    int fd = 0;
    fd = open(pid_file, O_RDWR|O_CREAT, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
    if(fd < 0)
    {
        printf("ERROR: can not open pid file %s\n",pid_file);
        return -1;
    }
    if(lockfile(fd)<0)
    {
        if(errno==EACCES || errno==EAGAIN)
        {
            close(fd);
            printf("WARNING: process already run\n");
            return 0;
        }
        printf("ERROR: can not lock pid file: %s error: %s\n", pid_file, strerror(errno));
        return -2;
    }
    if(dprintf(fd, "%d", pid)<0)
    {
        close(fd);
        printf("ERROR: write pid failed\n");
        return 0;
    }
    return fd;
}

int remove_pidfile(const char *pid_file, int fd)
{
    close(fd);
    remove(pid_file);
    return 0;
}

void handle_sig(int signo)
{
    if( (signo==SIGINT)|(signo==SIGTERM) )
        run=0;
}

int main()
{
    int fd = 0;
    const char *pid_file = "/var/run/test.pid"; 

    fd = creat_pidfile(pid_file,getpid());
    if(fd>0)
        close(fd);
    else
    {
        printf("create pid file error\n");
        return -1;
    }
    
    signal(SIGINT,handle_sig);
    signal(SIGTERM,handle_sig);

    while(run)
    {
        sleep(5);
    }

    remove_pidfile(pid_file,fd);
    
    return 0;
}

6. Debug #

在源文件里声明:

#ifdef __DEBUG__
#define DEBUG(format, ...) printr("%03d: "format"", __LINE__, ##__VA_ARGS__)
#else
#define DEBUG(format, ...)
#endif

调用 DEBUG() 的语法与 printf() 一样,编译时在 gcc 中声明 -D __DEBUG__ 即可打开调试信息。