linux下c语言基本知识点和使用

编译和链接

文件类型介绍

  • .c c语言源文件
  • .h 程序所包含的头文件
  • .i 已经预处理过的C源代码
  • .s 汇编语言源代码
  • .o 编译后的目标文件

gcc编译器使用

gcc可以编译其他的语言,具体有预处理,编译,汇编,链接四个步骤
常用选项

  • -o 生成目标
  • -c 取消链接步骤,编译源码并最后生成目标文件 也就是编译后停下来
  • -S 生成汇编语言文件后停止编译(.s文件) 也就是汇编后停下来
  • -L 指定链接库文件的路径,一般分为静态库和共享库
  • -l 指定要链接的库的名称
  • -I 指定头文件的目录
  • -D 传递宏定义
    要注意多文件的编译
  • 示范
    • gcc -Wall fun.c main_fun.c –o main_fun
    • gcc –Wall –c main_fun.c –o main_fun.o

正式内容

魔术变量

  • __FILE__ 文件名
  • __FUNCTION__ 函数名
  • __LINE__ 当前是第几行

标准流

程序在运行的时候会默认有三个流stdin(文件描述符为0),stdout(文件描述符为1),stderr(文件描述符为2)分别是标准输入,标准输入,错误信息 也就是说这三个变量是全局变量

常用类库(如果想知道函数的用法可以使用man查询)

可以安装手册来快速查看函数被使用是要的类库

  • stdio.h 标准输入输出库
    • fgets 从指定的流读取内容(一般常用这个代替scanf)
  • stdbool.h
    • bool 可以使用基本数据类型bool 这个类型代替的是_Bool
  • stdlib.h
    • exit 结束进程
    • system 创建一个子进程执行command
    • atoi 将字符串转化成整型
  • limits.h 里面定义了一些基本数据类型的最大值最小值
    • INT_MAX int类型的最大值
  • error.h 里面定义了错误的相关处理等信息
    • errno 错误的错误码
    • strerror 将错误码转化为实际的错误信息
  • string.h
    • bzero 将一段内存区域清零
  • time.h 里面定义了一些和时间相关的函数
    • time 获取时间戳
    • localtime 将时间戳转化为当前的时间 这个会返回一个tm_t的结构体,可以使用strftime来转换成指定格式
    • strftime 格式化时间
  • unistd.h unix标准头文件 里面定义了一些常用的unix函数
    • sleep 睡眠当前程序
    • close 关闭打开文件 参数是一个文件描述符
    • read write lseek 文件IO的相关函数
    • _exit 结束当前进程,但是不刷新标准输出缓冲区
  • fcntl 里面定义了文件IO的相关函数
    • open 以文件IO的形式打开文件 这个是可以打开设备文件的 返回值是一个文件描述符
  • dirent.h 关于目录读取的相关内容
    • opendir readdir closedir
  • sys/stat.h 存放系统相关命令的内容
    • chmod fchmod
    • stat lstat 获取文件属性
  • sys/types.h 存放一些固定的系统相关的结构体内容,大部分的库相关的结构体都会定义在这里
  • sys/wait.h
    • wait 等待子进程结束 并接收返回值
  • pthread.h posix标准的线程库 这个是第三方库,编译时要指定-lpthread 不然会出错
  • semaphore.h 信号量相关库
  • sys/ipc.h ipc通信相关库 ipc即进程system IPC 下面的库和这个搭配使用
    • sys/shm.h 创建共享内存相关库
    • sys/msg.h 消息队列相关库
    • sys/sem.h 信号灯集相关库
  • sys/socket.h 网络相关库
    • socket 创建一个套接字
    • bind 绑定套接字
    • listen 将套接字主动变为被动
    • accept 阻塞等待客户端请求
    • conncet 连接服务器
    • send,sendto,recv,recvfrom 接收发送数据
  • sys/select.h
    • select fd_set监听函数
  • sqlite3.h sqlite相关库
    • sqlite3_open 打开数据库
    • sqlite3_exec 执行sql语句

静态库和共享库

静态库是将被程序使用的函数等代码复制到程序中,所以程序在运行时无依赖,而共享库是将不将库文件复制到程序中,所以程序体积不会有太大变化,而且易于升级库文件,一般推荐使用共享库 共享库一般需要注意如何让系统能找到要加载的共享库

库的扩展名
  • 静态库 .a
  • 共享库 .so

进程相关

c语言创建进程要使用到unistd.h库中的fork函数 fork函数创建进程后子进程会复制父进程的所有资源,从fork的下一条指令开始执行,若父进程先结束,子进程将会被init进程收养,转为后台执行,若子进程先结束,如果父进程未回收好子进程,子进程将会变成僵尸进程
进程相关函数一般使用unistd.h库,但是这是在linux情况下,windows下面情况不确定

进程相关术语

  • 中断 中断一般是指在cpu正常工作时,硬件可以发出中断,cpu会马上处理中断,处理完后返回继续工作

进程类型

一般linux进程分为下面三种

  • 交互进程 shell就是这种类型
  • 批处理进程 顺序执行作业
  • 守护进程
守护进程

守护进程的生命周期一般比较长,通常在系统启动时运行,系统关闭时结束。Linux以会话、进程组的方式管理进程,每个进程属于一个进程组,会话是一个或多个进程组的集合,通常用户打开一个终端时,系统会创建一个会话,一个会话最多有一个终端,所有通过该终端运行的进程都属于这个会话,终端关闭时,所有该会话下的所有相关进程都会被释放
Linux下C语言守护进程创建

  1. 创建子进程,父进程退出
    • 这样是为了让子进程变成孤儿进程,被init进程收养 可以通过fork的返回值来判断是子进程还是父进程(返回值等于零为子进程)
  2. 子进程创建新会话
    • 这样是为了让子进程成为新的会话组长,同时脱离原先的终端
    • 脱离终端后 如果没有创建终端stdin/stdout/stderr 无法在使用
  3. 更改当前工作目录
    • 守护进程一直在后台运行,其工作目录不能被卸载 所以要重新设定当前工作目录
  4. 重设文件权限掩码
    • 文件权限掩码设置为0 只影响当前进程
  5. 关闭打开的文件描述符
    • 这样是为了关闭所有从父进程继承的打开文件
// 创建守护进程实例 创建一个守护进程 用来每隔一秒获取时间记录在time.log中
int main(){
    pid_t pid;
    FILE *fp;
    time_t t;
    int i;
    if((pid=fork())<0){
        perror("fork"); //创建子进程失败
        exit(-1);
    }
    else if (pid>0){
        exit(0); // 关闭父进程
    } 
    setsid();   // 开启新的会话
    umask(0);   //更改文件掩码
    chdir("/tmp")   // 修改工作目录
    for(i=0;i<getdtablesize();i++){ // 关闭父进程的文件描述符
        close(i);
    }
    if((fp=fopen("time.log","a"))==NULL){
        perror("fopen");
        exit(-1);
    }
    while(1){
        time(&t);
        fprintf(fp,"%s",ctime(&t));
        fflush(fp);
        sleep(1);
    }
}

进程间通信

  • 早期UNIX进程间通信
    • 无名管道
      • 无名管道只能用于具有亲缘关系的进程,而且是单工模式 创建时会返回两个文件描述符,分别用于读写管道 如果想要实现读写需要两个无名管道
    • 有名管道
      • 有名管道对应管道文件,可以用于任意进程之间通信 读写等操作靠的是文件IO 要先创建有名管道,然后读写端同时开启,如果只有一端的话,打开管道会阻塞 内容存放在内存中
    • 信号
      • 信号是在软件层次上对中断机制的一种模拟,linux通过信号通知用户进程,不同的信号代表不同的事件,进程有三种响应方式 缺省 忽略 捕捉
  • System V IPC 每个IPC对象都有一个唯一的ID,创建后会一直存在,直到被显式删除 且每个IPC对象有一个唯一的key
    • 共享内存
      • 共享内存是一种最高效的进程通信方式,进程可以直接读写内存,不需要任何数据的拷贝,所以效率最高,访问方式就好像直接访问内存,使用前要将内存进行映射而且使用时要注意同步和互斥配合使用
      • 使用步骤
        1. 创建/打开共享内存
        2. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
        3. 读写共享内存
        4. 撤销共享内存映射 如果没有主动撤销,进程结束自动撤销
        5. 删除共享内存对象
    • 消息队列
      • 消息队列就是一个消息的列表。用户可以在消息队列中添加消息,读取消息,消息队列可以按照类型来发送/接收消息 消息可以自定义,但是必须有一个long mtype成员,代表消息类型,其他成员都属于消息正文
    • 信号灯集
      • 信号灯集是一个或多个计数信号灯的集合,信号灯的解释可以看下面的信号量,不过这里的信号灯集是可以用于进程之间的通信的,信号灯集可以申请多个资源时解决死锁问题
      • 步骤
        1. 打开/创建信号灯
        2. 初始化信号灯 如果是创建的进程要初始化信号灯
        3. P/V操作
        4. 删除信号灯
  • 套接字

exec函数族

unistd.h
进程调用exec函数族执行某个程序 进程当前内容被指定的程序替换,可以实现父子进程执行不同的程序

  • execl 执行命令
  • execlp
int execl(char *path,char *arg,...)
int execlp(char *file,char *arg,...)
execv和execvp差别和上面类似 file会在环境变量PATH中找程序
int execv(char *path,char *argv[])
int execvp(char *file,char *argv[])
返回EOF代表失败 所有exec函数族最后一个参数要加NULL
examples
if(execl("/bin/ls","ls","-a -l","/etc",NULL)<0)

  • execv
  • execvp

进程相关联函数

默认函数不写库都在unsitd.h

  • 回收子进程
    • wait 随机回收一个子进程,那个先结束就先回收(子进程自动结束),回收未成功会处于阻塞状态 失败会返回EOF 可以通过一些特定的函数来获取子进程的返回值等其他信息
    • waitpid 回收指定pid的子进程 可以指定回收方式 参数这里不写出了
  • 进程通信相关函数
    • pipe 创建无名管道
    • mkfifo 创建有名管道文件
    • 信号 可能要使用signal.h
      • kill 对指定的进程发送信号
      • raise 对当前进程发送信号
      • alarm 指定定时器 到时间会向当前进程发送定时器信号 一个进程只能设定一个定时器 第二次调用会重新设定定时器
      • pause 进程阻塞 知道被信号中断
      • signal 指定信号的响应方式
      • 如果想要知道更多相关函数查阅手册
    • ftok 创建一个IPC所需要的唯一的key值
    • 共享内存
      • shmget 创建共享内存
      • shmat 映射共享内存地址
      • shmdt 撤销共享内存映射
      • shmctl 共享内存控制 可以通过执行这个函数进行删除共享内存等操作
    • 消息队列
      • msgget 打开/创建消息队列
      • msgsnd 向消息队列发送消息
      • msgrcv 从消息队列接收消息
      • msgctl 控制消息队列
    • 信号灯集
      • semget 打开/创建信号灯集
      • semctl 可以进行初始化信号灯集等操作
      • semop 信号灯P/V操作 需要用的特定的结构体

线程相关

下面相关函数在pthread.h库中
在任何一个线程中调用exit会导致进程结束

线程相关术语

  • 同步 同步是指多个任务按照约定的先后次序相互配合完成一件事
  • 信号量 可以用来实现线程间同步的一种机制,信号量代表某一类资源,其值表示系统中该资源的数量,资源足够就可以运行不然就会阻塞,可以通过信号量来实现锁 信号量是一个受保护的变量,只能通过三种方式访问
    • 初始化
    • P操作(申请资源) 资源不做会阻塞
    • V操作(释放资源) 通知系统资源数增加了
  • 临界资源 一次只允许一个任务(进程,线程)访问的共享资源
  • 互斥锁 使用互斥锁保证一个资源在访问时是临界资源

线程相关函数

  • pthread_create 创建线程
  • pthread_join 等待线程执行完成,并接受返回值
  • semaphore.h 信号量库
    • sem_init 初始化信号量
    • sem_wait P操作
    • sem_post v操作
  • pthread_mutex_init 初始化互斥锁
  • pthread_mutex_lock 获得锁
  • pthread_mutex_unlock 释放锁
  • pthread_cond_init 初始化条件
  • pthread_cond_wait 进入等待集合
  • pthread_cond_singal 唤醒等待集合
  • pthrad_exit 退出线程
  • fflush 刷新缓冲区
  • 这里省略了相关参数和需要的变量类型,如果需要请查手册

网络相关

基本知识点

c/s模式基本创建过程

TCP:

  • Server:socket(),bind(),listen(),accept()
  • Client:socket(),bind(),connect(),close()

UDP

  • Server:socket(),bind(),recvfrom(),sendto()
  • Client:socket(),bind(),sendto(),recvfrom()
网络字节序

一般来说本地字节序是采用大端法表示的,而网络字节序是采用小端法表示的

TCP/UDP编程

TCP相关函数(下面函数一般返回值小于0代表发生错误)
  • socket 创建一个套接字(下面是参数介绍和参数常用的值) 成功返回文件描述符
    • int domain
      • AF_INET,AF_INET6 ipv4和ipv6
    • int type 套接字类型
      • SOCK_STREAM 流式套接字 唯一对应于TCP
      • SOCK_DGRAM 数据包套接字 唯一对应于UDP
    • protocol 一般填0 使用原始套接字的时候需要注意
  • bind 绑定IP地址等信息
    • int sockfd 通过socket得到的fd也就是文件描述符
    • sockaddr *addr 这个参数一般不适用sockaddr这个结构体而采用sockaddr_in这个结构体,如果是IPV6使用sockaddr_in6或者sockaddr_storage,关于ipv6详细的信息使用man 7 ipv6查看
      • sockaddr_in 使用这个时先使用bzero将变量的内存区域清零
        • sin_family 协议类型 2字节
        • sin_port 端口号(端口为0代表自动分配端口) 2字节
        • sin_addr 地址 4字节
          • 绑定到任意IP上,如果这样写的话,只要发送到这个机器上,端口一致,协议一致就能被接收到,如果有两块网卡照样能接收到 sock_in.sin_addr.s_addr=htonl(INADDR _ANY)
    • socklen_t addrlen 地址长度
  • listen 监听端口,将主动套接字转化为被动套接字
    • sockfd 套接字的文件描述符
    • backlog 同时允许几路客户端与服务器正在连接(正在进行三次握手)一般填5 ARM最大为8
  • accept 阻塞等待客户端连接请求 ,成功时返回已经建立好连接的新的newfd(文件描述符)
    • 这个的参数和bind一致,不过用处不一样,这个的sockaddr可以获得连接客户端的ip地址等信息,如果不关心可以设置为NULL
  • connect 向服务器发送连接请求 后面的读取发送操作可以通过socket返回的文件描述符
    • 参数和bind一致,不过地址要新建一个sockaddr_in用来记录服务器的地址
  • send 发送数据到连接端(这个的用法和write差不多一致不过新增了一个参数flags)
    • flags 表示发送方式一般填0,下面是其他的值的意思
      • MSG_DONTWAIT 以非阻塞的方式发送,也就是立即发送
      • MSG_OOB 发送TCP类型的带外数据(out-of-band)
    • recv 接收来自连接端的数据(这个的用法和read差不多一致不过新增了一个参数flags)
      • flags 表示发送方式一般填0,下面是其他的值的意思
        • MSG_DONTWAIT 非阻塞版本
        • MSG_OOB 接收TCP类型的带外数据(out-of-band)
        • MSG 从缓冲区的顶端取数据,但是不改变缓冲区,也就是说缓冲区下一次读仍然还是原来的内容。
  • setsockopt
    • 用法:允许绑定地址快速重用
int b_reuse=1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
  • 字节序转换相关函数
    • inet_pton 将ip地址转化为网络字节序的形式 成功返回1
      • inet_pton(AF_INET,“192.168.12.12”,(void *)&target)
    • inet_ntop 将网络字节序转化为本地字节序的形式 成功返回1
      • inet_ntop(AF_INET,address,target,len)
    • 主机字节序转换成网络字节序
      • htonl,htons这两个唯一的不同在于接受参数的类型不同一个接受u_long,一个接受u_short
    • 网络字节序到主机字节序
      • ntohl,ntohs这个和上面的不同处相似
UDP相关函数(下面函数一般返回值小于0代表发生错误)
  • sendto 这个函数和recv差不多,但是多了发送者信息的参数也就是sockaddr这个类型,还有目标信息的长度,可以通过这个来获取是谁发送的数据
  • recvfrom 这个函数和recv差不多,但是多了目标信息的参数也就是sockaddr这个类型,还有目标信息的长度

IO多路复用模型

服务器端常用的IO模型有:

  • 同步阻塞IO
  • 同步非阻塞IO
  • IO多路复用模型 也称为异步阻塞IO
  • 异步IO 也称为异步非阻塞IO
介绍

多路复用IO即在同一个线程内同时处理多个TCP连接,最大优势是减少系统开销,不必创建/维护过多的线程
大致过程:

  1. 将关心的文件描述符加入到集合中(fd_set),文件描述符的分配是递增的
  2. 调用select/poll函数去监控fd_set中那些文件描述符(阻塞等待集合中文件描述符有数据)
  3. 当有资源更新时,select会停止阻塞并且更新fd_set,然后退出,在select之后fd_set中存放有数据的文件描述符
  4. 判断文件描述符是否在更新后的fd_set中
  5. 依次处理文件描述符中的数据
IO多路复用相关函数
  • fd_set 存放文件描述符的集合
    • fd_set的实现原理
      • 在linux中一个进程可以分配的文件描述符最多1024个,也就是0-1023,但是标准流占3个,所以我们取1024位就可以了,也就是128个字节,32个long型。
    • 下面时fd_set相关操作的宏定义
      • void FD_ZERO(fd_set *fdset) 将fd_set的内存区域清空
      • void FD_SET(int fd,fd_set *fdset) 添加一个文件描述符到fd_set中
      • void FD_CLR(int fd,fd_set *fdset) 在fd_set中删除一个文件描述符
      • int FD_ISSET(int fd,fd_set *fdset) 判断一个文件描述符是否在fd_set中
  • select 阻塞检查fd_set中是否有文件描述符数据更新(下面是参数介绍)
    • int maxfd 在fd_set中在该值下的文件描述符会被监听
    • fd_set *readfds 监听是否有可读数据的fd_set(这些如果fd_set如果没有可填NULL)
    • fd_set *readfds 监听是否有可写的fd_set
    • fd_set *readfds 异常fd_set
    • struct timeval *timeout 设置超时返回
  • epoll 此函数需要后续查找学习
  • poll 这个函数也可以用于多路复用,具体使用省略

网络扩展

相关函数介绍

下面的函数有些没有做详细的介绍,要知道详情可以使用man查询

  • gethostbyname 可以根据域名获取IP地址
  • setsockopt 设置套接字的属性,具体函数和相关参数设置参照图片存放处

网络超时优化

一般来说,套接字等待接收数据,如果没有数据会一直阻塞,如果对此方法不满意可以采取下面的解决办法

  • 采用多路复用解决
  • 使用setsockopt设置设置接收超时
  • 设置定时器,捕捉SIGALRM信号参考代码如下
void handler(int sinno){return ;}
struct sigaction act;
sigaction(SIGALRM,NULL,&act);
act.sa_handler=handler;
act.sa_flags &=~SA_RESTART;
sigaction(SIGALRM,&act,NULL);
alarm(5);
if(recv()<0)[]

网络连接保持

  • 每个一段时间,一方发送一点数据,对方给出特定的应答,如果超出设定次数大小的时间内没有应答,则认为异常
  • 通过改变套接字的属性来实现,具体看图片

广播,组播

  • 广播的步骤
    • 广播发送
      1. 创建用户数据报套接字
      2. 设置套接字属性,缺省创建的套接字不允许广播
      3. 接收方地址指定为广播地址,指定端口信息
      4. 发送数据包
    • 广播接收
      1. 创建用户数据报套接字
      2. 绑定本机IP地址和端口(绑定的端口必须和发送方指定的端口相同)
      3. 等待接收数据
  • 组播的步骤
    • 组播发送
      1. 创建用户数据报套接字
      2. 接收方地址为组播地址(一般组播地址为D类地址)
      3. 指定端口信息
      4. 发送数据包
    • 组播接收
      1. 创建用户数据报套接字
      2. 加入组播组
      3. 绑定本机IP地址和端口(绑定的端口必须和发送方指定的端口相同)
      4. 等待接收数据
// 加入组播代码
struct ip_mreq mreq;
bzero(&mreq,sizeof(mreq));
mreq.imr_multiaddr.s_addr=inet_addr(group_address);
mreq.imr_interface.s_addr=htol(INADDR_ANY);
setsockopt(fd,IPPORT_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

unix域套接字

unix域套接字,在易用性和效率上都有不错的效果,使用上面和TCP,UDP都差不多,不过改了域类型和bind的时候绑定地址有点特殊

unix域套接字在使用方法和TCP和UDP没什么区别,不过域类型改为AF_LOCAL或AF_UNIX,不过在绑定地址的时候,要注意使用sockaddr_un类型, 里面有两个参数一个是域类型(sun_family)AF_UNIX,一个是文件的绝对路径(sun_path),但是文件必须不存在,因为绑定成功后文件会存在在内存中

Sqlite3

请查找相关sqlite的手册,下面给出代码的示范,只包括简单使用

int scallback(void *data,int argc,char **argv,char **azColName){
    sqliteR *tdata=(sqliteR *)data;
    int i=0;
    int len=0;
    char buf[NDATAL/4];
    if(!tdata){
        return 0;
    }
    len=tdata->length;
    bzero(tdata->data[tdata->length], sizeof(tdata->data[0]));
    for(;i<argc;i++){
        bzero(buf, sizeof(buf));
        snprintf( buf,NDATAL/4-1,"%s:%s",azColName[i],argv[i]);
        strncat(tdata->data[len], buf, NDATAL/4);
    }
    tdata->length+=1;
    return 0;
}
void test(){
    Perror(sqlite3_open("/Volumes/Other/Code/dictionary/inline dictionary/inline dictionary/data.db", &db), "sqlite3 open");
    sqliteR data;
    data.length=0;
    char *zErrMsg=0;
    char *sql="select username,passWord from User limit 5";
    int rc;
    rc=sqlite3_exec(db,sql, scallback, (void *)&data,  &zErrMsg);
    printf("%d",rc);
    if( rc != SQLITE_OK ){
       fprintf(stderr, "SQL error: %s\n", zErrMsg);
       sqlite3_free(zErrMsg);
    }else{
       fprintf(stdout, "Operation done successfully\n");
    }
    sqlite3_close(db);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值