读书笔记之《Linux操作系统下C语言编程入门》

 

Linux操作系统下C语言编程入门

 

第一章 基础知识

1.Makefile文件格式:

target: components

rule

第一行表示依赖关系,第二行是规则

符号$@表示目标文件,$^表示所有依赖文件,$<表示第一个依赖文件

 

2.编译时加入-lm选项,才能链接函数库,也可以指定路径,通过-L/home/kevin/...的方式。

 

3.系统缺省库路径为/lib,/usr/lib和/usr/local/lib。

 

4.查找某个函数属于哪个库的通用方式:找sin函数所在的库,使用命令 nm -o /lib/*.so |grep sin>;~/sin 命令,然后看~/sin文件。

nm:从对象文件中列出符号

 

 

第二章 进程介绍

1.getpid()获得进程ID号,getppid()获得父进程ID号,都是返回pid_t类型。getuid()获得当前进程所有者的ID号,geteuid()获得进程的有效用户ID,getgid()和getegid()分别获得组ID和有效组ID。他们都包含在头文件中。

 

2.新建进程--pid_t fork();创建一个除了进程ID和父进程ID其他完全一样的进程。fork失败返回-1,在父进程中返回子进程ID,在子进程中返回0.

 

3.进程阻塞等待可以使用pid_t wait(int *stat_loc);,该系统调用会使父进程阻塞知道一个子进程结束或者父进程接收到一个信号。stat_loc是紫禁城退出状态。或者使用:pid_t waitpid(pid_t pid, int *stat_loc, int options);,该函数等待指定的紫禁城直到紫禁城返回。函数位于

 

4.可以使用exec族调用系统程序。

 

int execl(const char *path,const char *arg,...);

int execlp(const char *file,const char *arg,...);

int execle(const char *path,const char *arg,...);

int execv(const char *path,char *const argv[]);

int execvp(const char *file,char *const argv[]):

 

 

第三章 文件操作

1.文件打开

int open(const char *pathname,int flags);

int open(const char *pathname,int flags,mode_t mode);

其中,flags可取值为

O_RDONLY:以只读的方式打开文件.
O_WRONLY:以只写的方式打开文件.
O_RDWR:以读写的方式打开文件.
O_APPEND:以追加的方式打开文件.
O_CREAT:创建一个文件.
O_EXEC:如果使用了 O_CREAT 而且文件已经存在,就会发生一个错误.
O_NOBLOCK:以非阻塞的方式打开一个文件.
O_TRUNC:如果文件已经存在,则删除文件的内容.
而且,mode可用5位数字表示权限,00000.第一位表示设置用户 ID.第二位表示设置组 ID,第三位表示用户自己的权限位,第四位表示组的权限,最后一位表示其他人的权限.
每个数字可以取 1(执行权限),2(写权限),4(读权限),0(什么也没有)或者是这几个值的和
2.ssize_t read(int fd, void *buffer,size_t count);
read读到文件尾或者被信号中断,返回值小于count,如果是被信号中断且没返回数据,则返回-1并且设置errno为EINTR。如果读到文件尾返回0.

 

3.获得当前工作路径

char *getcwd(char *buffer, size_t size);

把当前工作路径复制到buffer中,如果buffer不够大,返回NULL.

char *get_current_dir_name(void)直接返回当前路径。

 

 

4.操作Linux目录函数在头文件中,可以mkdir,opendir,readdir,closedir等。需要用到struct dirent结构体保存目录信息。

 

5.使用int pipe(int fildes[2]);可以创建一个管道,用于过滤和重定向程序,当调用成功后,可以访问文件描述符fildes[0]用于读文件, fildes[1]用来写文件。

 

6.使用int dup2(int oldfd,int newfd);来实现重定向操作,dup2将把oldfd描述符和newfd描述符对调,同时关闭newfd,即关闭原来的oldfd。所有向newfd操作都转到oldfd

 

7.STDOUT_FILENO,STDIN_FILENO,STDERR_FILENO,都定义在中,分别表示标准输出,输入,错误流的文件描述符编号,把这些流看作一个普通文件流。

 

 

第四章 时间概念

1.时间相关头文件为,系统当前时间可以通过time_t time(time_t *tloc);或char *ctime(const time_t *clock);获得。time函数返回从1970年1月1日0点以来的秒数,没什么实际意义,

ctime函数将当前时间转化为字符串,固定长度为26。

 

2.计算程序运行的时间可以使用int gettimeofday(struct timeval *tv,struct timezone *tz);函数,存在于头文件中,使用结构体为

strut timeval {

long tv_sec; /* 秒数 */

long tv_usec; /* 微秒数 */

};

 

3.计算时间差时,需要先把timeval中的tv_sec差值诚意1000000,加上tv_usec的差值,最后再除以1000000,才可以得出正确值。

 

4.内部间隔计时器

ITIMER_REAL:减少实际时间.到时的时候发出 SIGALRM 信号.

ITIMER_VIRTUAL:减少有效时间(进程执行的时间).产生 SIGVTALRM 信号.

ITIMER_PROF:减少进程的有效时间和系统时间(为进程调度用的时间).这个经常和上面一个使用用来计算系统内核时间和用户时间.产生 SIGPROF 信号.

 

5.利用计时器的常用函数

int getitimer(int which,struct itimerval *value);

int setitimer(int which,struct itimerval *newval, struct itimerval *oldval);

getitimer得到间隔计时器的时间,保存在value。setitimer设置计时器值为newval,并保存旧值在oldval。which表示使用3该计时器的哪一个

 

 

第五章 信号处理

1.信号类似于windows下的事件,有一个信号发生时,相应的信号就发送给相应的进程,linux下使用kill -l命令查看系统定义的信号。

 

2.信号来源于硬件的原因或者软件的原因,最常用的发出信号系统函数为kill, raise, alarm,和setitimer

 

3.int kill(pid_t pid,int sig);

如果pid_t为正,向其发送信号sig;

如果为0,向所有和pid_t进程在同一进程组的进程发送sig;

如果为-1,发送sig给所有进程表中的进程,除了进程号最大的进程;

如果小于-1,和0一样,只是发送的进程组是-pid_t。

 

4.int raise(int sig);发送sig信号给自己这个进程。

 

5.unisigned int alarm(unsigned int seconds);在seconds秒后发送SIGALRM信号给自己,SIGALRM信号的缺省操作是结束进程。

 

6.信号屏蔽相关操作

int sigemptyset(sigset_t *set);初始化信号集合,设置为空

int sigfillset(sigset_t *set);初始化信号集合,设置为所有信号。

int sigaddset(sigset_t *set,int signo);将信号signo加入信号集合中

int sigdelset(sigset_t *set,int signo);将信号signo从信号集合中删除

int sigismember(sigset_t *set,int signo);查询信号signo是否在信号集合中

 

int sigprocmask(int how,const sigset_t *set,sigset_t *oset);在设置好信号集合set后,将指定信号集合set加入进程的信号阻塞集合,如果提供oset,那么当前进程信号阻塞集合保存在oset中。how决定以下操作方式中的一种

SIG_BLOCK:增加一个信号集合到当前进程的阻塞集合之中.

SIG_UNBLOCK:从当前的阻塞集合之中删除一个信号集合.

SIG_SETMASK:将当前的信号集合设置为信号阻塞集合.

 

7.当某个信号被阻塞/屏蔽时,进程不会对该信号做出反应,但是信号会加入信号阻塞集合,并没有丢弃信号,一旦信号屏蔽取消,这个信号就会发生作用。

 

8.在屏蔽某个信号后,又接受到这个信号,希望对信号发出者做一个反应,例如说“当前无法进行”之类的动作时,可以使用sigaction函数。

 

 

第六章 消息管理

1.Linux只实现了POSIX标准提出的有名,无名信号量中的无名信号量。相关函数存在于中。

int sem_init(sem_t *sem,int pshared,unsigned int value);//创建信号灯,初始化为value,pshared决定信号灯能在几个进程间共享,(目前Linux未实现进程间共享,所以取0).

int sem_destroy(sem_t *sem);//删除信号灯

int sem_wait(sem_t *sem);//阻塞进程,直到信号灯的值大于0,函数返回时,自动将信号灯的值减一。

int sem_trywait(sem_t *sem);//于sem_wait相同,不过不阻塞

int sem_post(sem_t *sem);//与sem_wait相反,信号灯的值加一

int sem_getvalue(sem_t *sem);//得到信号灯的值

 

编译包含上面几个函数的程序需要加上 -lrt选项,以链接librt.so库

 

2.Linux实现流System V信号量。包含相关头文件有

key_t ftok(char *pathname,char proj);//创建关键字

int semget(key_t key,int nsems,int semflg);//创建信号量,成功时返回信号ID;key_t可以是ftor创建也可以是IPC_PRIVATE由系统选用;nsems表示创建信号个数,semflg创建权限标志,与文件权限相同

int semctl(int semid,int semnum,int cmd,union semun arg);//对信号量进行控制,semid为信号标志,semnum为信号序号(第几个),cmd为操作命令,常用命令有SETVAL(设置信号量的值)和IPC_RMID(删除信号灯);arg为给cmd的参数。

int semop(int semid,struct sembuf *spos,int nspos);//对信号进行操作,semid为信号标志,spos为操作数组表明要进行什么操作,nspos表明数组个数。

struct sembuf {

short sem_num; /* 使用哪一个信号 */

short sem_op; /* 进行什么操作,如果大于0,操作将sem_op加入信号量的值,并唤醒;

              /* 如果为0,当信号量值为0时函数返回,否则阻塞直到信号量的值为0;

              /* 如果小于0,函数判断信号量的值加上这个负值如果为0唤醒等待信号量为0的进程,如果小于0阻塞如果大于0那么从信号量中减去这个值并返回 */

short sem_flg; /* 操作的标志 */

};

 

3.命令ipcs查看信号灯,ipcrm删除信号灯

 

4.System V的消息队列是一种管道通信方式,主要函数定义在

int msgget(key_t key,int msgflg);//与semget一样,返回一个消息队列的标志

int msgsnd(int msgid,struct msgbuf *msgp,int msgsz,int msgflg); //用于消息通讯,msgp为消息内容,其中msgtype是必须包含的;

//msgsz消息大小,msgflg指出缓冲区用完时候的操作,一般为0

int msgrcv(int msgid,struct msgbuf *msgp,int msgsz,long msgtype,int msgflg); //用于消息通讯,msgflg指出无消息时候的处理,一般为0.

//msgtype指出接收消息时的操作,如果为0,接受消息队列第一个消息

//大于0,接收队列中消息类型等于这个值的第一个消息

//小于0,接收消息队列中小于或者等于msgtype绝对值的所有消息中的最小一个消息。

int msgctl(Int msgid,int cmd,struct msqid_ds *buf);//对消息进行控制

struct msgbuf {

long msgtype; /* 消息类型 */

....... /* 其他数据类型 */

}

 

5.System V提供了共享内存的通信方式,包含在中,函数主要有

int shmget(key_t key,int size,int shmflg);//size要创建共享内存大小

void *shmat(int shmid,const void *shmaddr,int shmflg);//用于连接共享内存,使用之前调用得到共享内存开始地址

int shmdt(const void *shmaddr);//用于断开共享内存,共享内存使用结束后调用

int shmctl(int shmid,int cmd,struct shmid_ds *buf);

 

 

第七章 线程操作

1.线程共享程序代码,线程创建函数如下,定义在,在编译时需要加入参数-lpthread以链接库。

int pthread_create(pthread_t *thread,pthread_attr_t *attr, void *(*start_routine)(void *),void *arg); //创建一个线程,thread表示创建的线程ID, atr指出线程属性

//start_routine表示线程创建成功后开始执行的函数

//arg为传递给start_routine的参数

void pthread_exit(void *retval);//用于退出线程,类似exit,会释放所有资源,然后阻塞等待其他线程使用pthread_join等待它。retval不可以是指向函数的局部变量

int pthread_join(pthread *thread,void **thread_return);//与wait调用一样等待指定的线程。

 

 

 

第八章 网络编程

1.netstat命令可以用来显示网络连接,路由表和接口统计等网络信息,常用选项为-an显示网络状态。

 

2.Linux提供套接字socket来进行网络编程,同样的一个连接表示为一个文件描述符,可以通过向描述符读写操作实现网络数据交流。

 

3.int socket(int domain, int type, int protocol);

//domain说明网络程序主机所在的通讯协族(AF_UNIX和AF_INET等),AF_UNIX只能用于单一的Unix系统进程间通信,而AF_INET是针对Internet。

//glibc实现的posix是用PF*代替流AF*,不过都可以使用。

//type说明的是我们采用的通讯协议(SOCK_STREAM, SOCK_DGRAM)

//protocol一般用0代替。

 

4.int bind(int sockfd, struct sockaddr *my_addr, int addrlen);//成功返回0.

//my_addr,在定义,但一般情况下,进行结构填充时,使用另一个等价的结构struct sockaddr_in来代替更为方便,定义在,而在bind等函数中再转换为sockaddr。

struct sockaddr_in{

unsigned short sin_family; //一般为AF_INET

unsigned short int sin_port; //我们要监听的端口号

struct in_addr sin_addr; //设置为INADDR_ANY表示可以和任何主机通信

unsigned char sin_zero[8]; //数据填充

}

 

5.int listen(int sockfd, int backlog);//监听,将文件描述符变为监听套接字

//backlog设置请求排队的最大长度。

 

6.int accept(int sockfd, struct sockaddr *addr, int *addrlen);

//sockfd为监听后的文件描述符

//addr,addrlen是给客户端程序填写的,服务端只需要传递指针。

 

7.int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);//客户端连接服务端,成功返回0

//serv_addr存储服务器端的连接信息,sin_add为服务端地址

 

8.字节转换函数,不同机器表示数据的字节顺序是不同的。

unsigned long int htonl(unsigned long int hostlong)

unsigned short int htons(unisgned short int hostshort)

unsigned long int ntohl(unsigned long int netlong)

unsigned short int ntohs(unsigned short int netshort)

 

9.IP与域名的转换,定义在

struct hostent *gethostbyname(const char *hostname)//将域名转化为结构指针

struct hostent *gethostbyaddr(const char *addr,int len,int type)//将32位的IP地址转换为结构指针

这2个函数

struct hostent{

char *h_name; /* 主机的正式名称 */

char *h_aliases; /* 主机的别名 */

int h_addrtype; /* 主机的地址类型 AF_INET*/

int h_length; /* 主机的地址长度 对于 IP4 是 4 字节 32 位*/

char **h_addr_list; /* 主机的 IP 地址列表 */

}

 

10.字符串IP地址和32位IP的转换,网络上IP为数字与点构成,struct in_addr中是32位的IP,需要转换函数

int inet_aton(const char *cp,struct in_addr *inp)//网络IP转换为32位IP

char *inet_ntoa(struct in_addr in)//32位IP转换为网络IP

 

11.服务信息获取函数

int getsockname(int sockfd,struct sockaddr *localaddr,int *addrlen)

int getpeername(int sockfd,struct sockaddr *peeraddr, int *addrlen)

struct servent *getservbyname(const char *servname,const char *protoname)

struct servent *getservbyport(int port,const char *protoname)

 

struct servent

{

char *s_name; /* 正式服务名 */

char **s_aliases; /* 别名列表 */

int s_port; /* 端口号 */

char *s_proto; /* 使用的协议 */

}

 

12.网络中使用write或者read函数时,如果返回小于0,需要根据错误类型处理

EINTR:中断错误

EPIPE:网络流断开,网络出现问题

ECONNRESET:网络连接被重置

 

18.一般网络中发送的数据,例如自定义结构等都是转化为char类型发送,接受再转化回来。

 

19.UDP协议程序常用函数

int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);

int sendto(int sockfd, const void *msg, int len, unsigned int flags, struct sockaddr *to, int tolen);

//buf或msg表示接受或发送的数据的缓冲区,len表示缓冲区大小

//from或to表示数据源或目的地的地址,fromlen或tolen表示地址长度

 

20.网络高级读写函数

int recv(int sockfd, void *buf, int len, int flags);

int send(int sockfd, void *buf, int len, int flags);

//flags可以是0或者以下组合

| MSG_DONTROUTE | 不查找路由表 |//send使用的标志,表示send告诉IP协议,目的主机在本网络地址上,无须查找路由表,一般用于网络诊断和路由程序中。

| MSG_OOB | 接受或者发送带外数据 |//表示可以接受和发送带外的数据。

| MSG_PEEK | 查看数据,并不从系统缓冲区移走数据 |//recv使用的标志,表示只从系统缓冲区中读取内容而不清除。一般多进程读写数据时可以使用。

| MSG_WAITALL | 等待所有数据 |//recv使用的标志,表示等到所有的信息到达才返回,recv一直阻塞直到满足条件或错误发生。

 

21.recvmsg 和 sendmsg 可以实现前面所有的读写函数的功能.

int recvmsg(int sockfd,struct msghdr *msg,int flags)

int sendmsg(int sockfd,struct msghdr *msg,int flags)

struct msghdr

{

void *msg_name;//UDP连接时,存储接收和发送方地址,实际上是指向struct sockaddr的指针;TCP连接时,为NULL

int msg_namelen;//msg_name长度,tcp连接时,为NULL

struct iovec *msg_iov;//接收和发送缓冲区内容

int msg_iovlen;//msg_iov的大小

void *msg_control;//接收和发送控制数据

int msg_controllen;//控制数据的长度

int msg_flags;//指定接收和发送选项,于recv,send选项一样。

}

struct iovec

{

void *iov_base; /* 缓冲区开始的地址 */

size_t iov_len; /* 缓冲区的长度 */

}

 

22.套接字关闭,close或者shutdown

int shutdown(int sockfd, int howto)//tcp是双向的,close会把读写通道都关闭,如果只要关闭一个通道,使用shutdown。

//howto: 0,关闭读通道,可以继续往socket写;

1,关闭写通道,可以继续从socket读;

2,关闭读写通道,与close一样,多进程程序里,几个子进程共享一个socket,如果使用shutdown那么所有子进程都无法操作,只能使用close关闭子进程的socket。

 

23.

网络7层架构:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层。

IP协议是在网络层的协议;

ICMP为消息控制协议,处于网络层,如果传递IP数据包时发生错误,就会使用ICMP报告错误

UDP是建立在IP基础之上的协议,处于传输层,与IP一样都是不可靠传输。

TCP也是建立在IP基础之上的协议,是按顺序发送的可靠传输;

 

24.TCP三次握手协议:

第一步,客户机建立并发送TCP数据包,设置SYN位为1,设置序列号seq为1000(假设)

第二步,服务端收到数据包,发现SYN为1,知道是请求连接,于是发送TCP数据包给客户机,将ACK设置为1,sak_seq设置为1000+1,设置自己的序列号seq为2000(假设)

第三步,客户机收到响应的数据包,从ACK为1和ack_seq为1001直到是服务端来的确认信息,因此客户机再发送TCP数据包确认,设置ACK为1,ack_seq为2000+1,seq为1001,客户机完成TCP连接。服务端收到这个确认数据包后也完成连接。

 

25.套接字选项:设置,获取,以及ioctl

int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen)

int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)

//level 指定控制套接字的层次.可以取三种值: 1)SOL_SOCKET:通用套接字选项. 2)IPPROTO_IP:IP 选项. 3)IPPROTO_TCP:TCP 选项.

//optname 指定控制的方式(选项的名称);

//optval 获得或者是设置套接字选项.根据选项名称的数据类型进行转换

选项名称 说明 数据类型

SOL_SOCKET

------------------------------------------------------------------------

SO_BROADCAST 允许发送广播数据 int

SO_DEBUG 允许调试 int

SO_DONTROUTE 不查找路由 int

。。。。

 

26.服务器服务模型:

循环UDP服务器:一次服务一个UDP请求

循环TCP服务器:一次服务一个TCP连接的所有请求

并发TCP服务器:创建多个进程,但创建时系统资源消耗大

并发TCP服务器,多路复用IO:利用select完成TCP连接的依次服务,但可能有的客户会等待很久

并发UDP服务器:创建多个进程,少用。

 

27.原始套接字:SOCK_RAW,只有root权限的人创建

int sockfd(AF_INET, SOCK_RAW, protocol);//创建原始套接字,Protocol可选IPPROTO_ICMP,IPPROTO_TCP,IPPROTO_UDP等等

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(读书笔记之《Linux操作系统下C语言编程入门》)