Unix/linux C(uc)与标准C语言最大的区别在于,标准C只是一种编程语言,而在此基础上还依赖于具体的操作系统,脱离了unix/linux系统的uc在其他地方可能不被识别。
(1)Unix/Linux系统的基本概述
用gcc将源文件翻译为可执行文件过程:
预处理:主要宏替换,头文件替换。
宏替换:#define用一个自定义的量代替一个数,如果要修改这个量的值,只要在最初的定义处修改即可。同时也定义了一些预定义宏:
__FILE__ -主要用于获取当前文件名%s
__LINE__ -主要用于获取当前宏所在的行号 %d
__DATE__ -主要用于获取日期信息 %s
__TIME__ -主要用于获取时间信息 %s
头文件替换:#include “a.h”表示先从当前程序所在目录开始寻找,然后去系统默认目录,#include <a.h>表示表示从系统默认所在目录开始寻找。
预处理指令:全部都以#开头
#pragma GCC dependency 文件名
=>表示当前文件依赖于指定的文件名,如果指定文件名的最后一次修改时间晚于当前文件,则产生警告信息
#pragma pack(整数n)超过4按4计算
=>主要用于设置对齐和补齐方式
#warining 字符串
=> 表示产生一个警告信息
#error 字符串
=> 表示产生一个错误信息
编译:检查语法的错误,同时将源文件翻译为汇编文件.s
汇编:将汇编文件转化为目标文件.o
链接:若编译时遇到调用其他文件的函数或变量时,会留下一个接口,链接部分则是找到这些接口要调用的函数或变量所在位置,将这些目标文件整合到一起,转化为可执行文件。
库文件:
一般为了调用者或者使用者的方便,会将完成一个具体功能是所有.o文件打包成一个库,调用或使用时只需要这个库文件和其对应的头文件即可。库文件分为两种:静态库和动态共享库。
静态库:使用这个库也就是链接时,如果要调用其他文件的函数或变量,会直接拷贝过来,所以目标文件会很大,但是运行时可以脱离静态库,不需要各种跳转。
动态共享库:使用这个库也就是链接时,如果要调用其他文件的函数或变量,会将要调用的函数或变量所在的地址拷贝过来,所以目标文件小,但是运行时不能脱离这个库,要各种跳转。可通过dlopen函数打开/加载共享库,dlsym函数在共享库中查找指定的函数地址信息。
函数出错:
1.对于返回值为int的函数,返回值是负数则代表出错,所以如果int型函数要返回一个负数值一般通过一个指针带出去,而返回值只表示是否出错,-1为出错,0为正常,返回值是指针则NULL表示出错,正确则是要返回的地址。
2.#include<error.h> errno是一个int型的全局变量,当程序运行出错会自动将出错原因序号值赋给errno。可以通过sterror(errno)输出对应errno的出错原因,也可以直接用perror(char *s)打印出&s:最近一次出错原因
main函数的原型:
int main(int argc,char* argv[]) 可以外部设定运行这个程序时的命令行参数值和每一个参数的位置
(2)Unix/Linux系统下的内存管理技术
在对文件的打开,读,写这些操作中,标C与uc相比效率更高,因为标C有标准输入输出的缓冲区,对于uc是用文件描述符来进行读写,文件描述符是一个整数,它在内核里对应着一个文件表,存储了当前文件的各种信息,当close()时是将该整数与文件表关系脱离,如果该文件表不再对应任何一个文件描述符则删除。
文件描述符是从3开始,0,1,2分别代表标准输入输出和标准错误。
利用fcntl函数可以对该文件描述符复制,获取状态,实现文件锁。
(3)Unix/Linux系统下的进程管理
程序最开始只有一个进程,也就是父进程运行,当遇到fork()函数时会创建一个新的进程叫子进程,同时fork之后的程序会由父进程和子进程同时开始执行,但我们当然希望他们分工干不同的事情,因此通过fork之后的返回值来判断是父进程还是子进程,若为0,则表示当前是子进程,若不为0则表示当前是父进程,同时返回的值表示子进程的进程号,PID表示进程编号,用getpid函数获取。
使用fork创建的子进程,会将父进程的内存区域除了代码区复制一遍,因此子进程的运行是在fork之前的运行结果基础之上开始的,但是之后子进程和父进程分别有各自的内存区域,除了代码区会共享。
父进程可利用wait函数来挂起当前进程,直到一个子进程结束并返回子进程结束运行状态。
Vfork()函数与fork()的区别是创建的子进程会直接占用父进程的内存区域而不是复制,导致父进程暂时挂起,一般与exec系列的函数搭配使用让子进程去执行一个别的程序,成为一个全新的进程,同时解除父进程的阻塞状态。如果用fork+exec则要复制一遍内存区域,而对于马上执行其他程序的子进程来说是多此一举。
(4)Unix/Linux系统下的信号处理
信号本质上来说就是一种软件中断,信号可以通过键盘(Ctrl+C等等)、程序出错(段错误信号11 SIGSEGV)、特定发送信号的函数(kill(),sleep()等等)来发出,信号是异步的,对于程序而言并不知道什么时候信号会到来,但一旦收到一个信号会立即进行处理。处理的方式有三种,默认处理(终止程序)、忽略处理、自定义处理(运行特定的函数),可通过signal函数进行设定。也可通过特定函数将所有信号屏蔽,待恢复之后再查看屏蔽期间收到过的信号。
(5)Unix/Linux系统下的进程间的通信
重点:
通信的主要方式
(1)文件
(2)信号
(3)管道
(4)共享内存
(5)消息队列(重点)
(6)信号量集
(7)网络通信(重点)
1.文件
多个进程可通过访问同一个文件来进行通信
2.信号
不同进程间可通过kill()函数等向其他进程发送信号
3.管道
管道的本质还是文件,分为有名管道(任意通信)和无名管道(父子进程间通信)
有名管道: mkfifo a.pipe(创建管道)
echo hello > a.pipe(数据不能写进去)
另外一个中断中输入:
cat a.pipe(数据传递到这里)
因此管道实际上并不能存储数据,它只是一个传递数据的通道
无名管道:int pipe(intpipefd[2])返回两个文件描述符,其中pipefd[0]表示是读端,pipefd[1]表示是写端。
4.共享内存
本质上是内核维护的一块内存区域,两个进程可以共同使用这一块内存区域来进行通信。过程开始与消息队列类似要生成一个key值,通过key值创建或获取共享内存地址,用shmat()函数将共享内存地址挂接到本进程地址空间上,使用完毕后要脱接。
5.消息队列
将消息放到消息队列中后,由其他进程进行读取,这个消息队列会一直持续到内核重启,如何获取消息队列是通过key值,双方通过约定一个文件路径和ID号,用ftok()来生成一个相同key值,通过该key值就能用msgget()函数获取到消息队列号,类似于文件描述符,双方可通过该消息队列进行通信。
6.信号量集
信号量集本质上是一个计数器,多个进程要同时访问一个共享资源时,可设定一个信号量集表示当前可访问共享资源的进程数,每当有一个进程要访问资源,该信号减1,当一个进程访问完毕该信号加1,当该信号变为0时表示访问该资源的进程数已达最大值,要等待。
7.网络通信
常用的网络协议:
TCP:传输控制协议,面向连接的协议,类似于打电话
UDP:用户数据报协议,非面向连接的协议,类似于发短信
IP:通过IP可以定位到具体的一台电脑
端口:通过端口号可定位到这台电脑上的一个具体运行的进程
Socket:参考上篇博客
(6)Unix/Linux系统下的多线程编程
线程隶属于进程,进程是重量级单位,每当创建一个新的进程要新开辟一块内存,每个进程有独立的内存区域,而线程是轻量级单位,新创建的线程共享内存资源,但每个线程都有独立的栈区。多个线程相互独立又相互影响,当访问同一个文件时一般采用互斥锁。
本文详细介绍了Unix/Linux系统下C语言编程的特点,包括gcc编译过程、预处理指令、库文件类型、错误处理方法以及内存管理、进程管理、信号处理、进程间通信等多个方面,深入探讨了Unix/Linux环境下的C语言编程技术。
833

被折叠的 条评论
为什么被折叠?



