UnixC

预处理指令:

    #pragma

    head.h文件不能比当前文件新

    #pragma GCC dependency "head.h"

    不能使用单词add printf

    #pragma GCC poison add printf

    _Pragma等价于#Pragma

   

创建静态库:

1)gcc -c xxx.c 生成 .o 文件

2)ar -r lib静态库名.a xxx.o ...

3)使用静态库

将静态为文件 libxxx.a所在的路径配置到环境变量LIBRARY_PATH下

4)连接静态库

gcc xxx.o -l静态库名

如果环境变量LIBRARY_PATH没有配置静态库的路径,

可直接连接:gcc xxx.o -l静态库名  -L库文件的在路径

还可以直接连接:gcc xxx.o libxxx.a

共享库(动态库):

    1)创建共享库

        gcc -c -fpic xxx.c ... 生成目标 xxx.o

gcc -shared xxx.o -o lib库名.so

2)使用共享库

1.连接

gcc xxx.o -l库名 -L 共享库路径,或者把共享库文件所在的目录配置到环境变量LIBRARY_PATH

2.运行 a.out

要求共享库在LD_LIBRARY_PATH环境变量指定的路径下能找到共享库文件

-----------

 

ldd:

    查看程序所依赖的动态库


 

预处理:#include<head.h>

默认位置(/usr/include) C_INCLUDE_PATH -I 指定的位置 CPATH

C++ : CPLUS_INCLUDE_APTH

连接:找库(静态库,共享库)

在LIBRARY_PATH中找 直接用gcc的选项直接指定: -L  位置

运行时:需要共享库LD_LIBRARY_PATH

静态库:

xxx.c        gcc -cxxx.c ==> xxx.o

yyy.c        gcc -cyyy.c ==> yyy.o

ar -r libmyku.a xxx.o yyy.o

myku.h

test.c : #include "myku.h"

gcc -c test.c

gcc test.c -lmyku -L . ==>a.out

共享库:

xxx.c        gcc -c-fpic xxx.c ==> xxx.o

yyy.c        gcc -c-fpic yyy.c ==> yyy.o

gcc -shared xxx.o yyy.o -o libmyku.so

myku.h

test.c : #include "myku.h"

gcc -c test.c

gcc test.o -lmyku -L . ==> a.out

要使a.out能够运行则在LD_LIBRARY_PATH中要有libmylu.so所在路径

或者加上-static参数进行静态编译

显式手工使用共享库:

 

错误处理:

errno.h

定义了全局变量 int errno

程序出现错误时(一般返回非0值, 或返回空指针)

需要错误信息时:

perror()

printf("%m");

strerror(errno);

不要根据errno判断程序是否出错,而是出错去查看errno以知道出现什么错误

环境变量:

在任意程序中都能使用一个全局变量:char** environ

在使用时:

extern char** environ;

环境表是一个字符串数组,最后一个元素是NULL(*environ中的最后一个元素)

int main(int argc, char** argv, char** env)

参数env和environ有同样效果

getenv(name);

putenv(str); //str : name=value

setenv(name,value,rewrite);

unsetenv(name);

clearenv(); //仅Linux支持

在程序中改变环境变量不会影响系统环境变量

 


内存分配:

内存分配:

    STL,智能指针

|

new, delete(c++)

|

|

malloc, free(c)

|

|

    brk, sbrk , mmap, munmap       用户级

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

内核级   

kmalloc  vmalloc 

|

|

    get_free_page

 

STL分配内存和释放是自动的

vector<int> v;

sizeof(v) : 12

指针

当前数据量

最大容量

C++ : newdelete

  new[] delete[]

C        :malloc free

newmalloc的区别:

new : 1,分配内存空间

  2,构造成员变量

  3,构造函数

  4,类型转换 void* ==> 用户类型

malloc : 只做内存分配

delete : 1,调用析构函数

 2,释放空间

free   : 只释放空间

malloc:

在内存中维护一个数据结构(链表)

一次malloc会形成一个链表的节点

struct mem {

分配的内存空间

上一块内存的地址

下一块内存的地址

此节点中分配的内存空间的大小

}

malloc系列函数分配的内存绝对不允许越界

一旦越界,就会破坏后台内存的数据结构

 

全局常量在代码区

局部常量在栈区

局部静态变量在全局区

 

内存管理:

DOS IBM-DOS MS-DOS 物理地址 非保护

WIN32        保护模式逻辑地址(虚拟地址) 用户程序不能访问物理地址

 

32位操作系统中地址由32个二进制位(4字节)

int* p = sbrk(4);

void* p = sbrk(0);//未分配空间的首地址

void* p = sbrk(-4);//释放空间

 


1,内存:

使用的地址是虚拟地址(逻辑地址),

要与真正的物理内存映射(对应)起来才可以使用,否则段错误

操作系统对内存的分配以页为单位,一页一般4K

2,malloc

malloc在后台是一个双向链表的数据结构,每一次调用形成一个链表接节点

小内存:

第一次给几十个页面,后面的调用会在此页中分配

大内存:

第一次调用分配所需要的页面,后期调用在本段内存的基础上扩充

3,brk/sbrk

后台维护一个位置,刚刚已分配空间的结束位置(下次要分配空间的开始位置)

sbrk(0);//下次要分配空间的开始位置

sbrk(4);//分配4个字节的空间,返回这4个字节的首地址

sbrk(-4);释放空间,把位置向前移4个字节

brk(ptr);//ptr是需要分配的空间的结束位置的地址,将后台维护的位置移到ptr处。此时用sbrk(0)返回的地址就是ptr

4、mmap

 


I/O:

文件描述符:

非负整数, 0 1 2 操作文件依据 代表一个打开的文件

每个文件描述符对应一个文件,

当open一个文件时,内核会在内存中船舰一个文件表

文件表记录文件状态,当前偏移量,指向文件系统(磁盘上)的i-node指针

对文件的操作主要是一些系统调用函数:

open        read        write        close

文件描述符=open("文件名",[O_RDONLY, O_WRONLY, O_RDWR | O_APPEND |

O_CREAT, O_EXCL, O_TRUCK], 文件权限数字表示)

ssize_tsize = read(fd, void*, ssize_t);

ssize_tsize = write(fd, void*, ssize_t);

 

lseek:

改变文件偏移量,不会进行I/O操作

文件偏移量可以大于文件大小,这时对文件进行操作,造成文件空洞

文件空洞不会真正在磁盘上占用空间

dup

复制文件描述符,但并不赋值文件表

dup2

指定复制后的文件描述符,如果此文件描述符已被占用,则先关闭

sync/fsync/fdatasync

同步数据 功能如同标准C中的fflush

fcntl:

操作文件的一些特性

int(命令不同含义也不一样)=fcntl(fd, 命令(int), 没有/long/flock)

F_DUPFD 复制文件描述符,类似dup, dup2

fcntl(fd, F_DUPFD, 0) == dup(fd)

close(7); fcntl(fd, F_DUPFD, 7) == dup2(fd, 7)

前者非原子操作,后则原子操作

F_GETFD/F_SETFD 有特殊用处 exec系统函数联合使用

F_GETFL/F_SETFL 获取(修改)文件描述符的状态信息

O_RDONLY, O_WRONLY, O_RDWR, O_APPEND

int stat = fcntl(fd, F_GETFL, 0);

stat & O_APPEND 如果非零,则文件有O_APPEND属性

int r = fcntl(fd, F_SETFL, O_APPEND | O_NONBLOCK);

F_SETLKW?F_SETLK 测试(设置)在文件身上的锁(读锁,写锁)

对文件进行加锁和解锁

加锁:

加读锁, 加写锁

读锁: 共享锁 同时对一个文件可以加多个读锁

写锁: 独占锁 排他的,对一个文件区域最多只能加一个写锁

如果加上读锁可以再加读锁,但不能加写锁

如果加了写锁不可以再加读锁和写锁

 

fcntl(fd, F_SETLK, flock);

flock决定着加读锁还是写锁,还有加到什么地方


1.文件加锁:加在文件的某一个区域

读 写 fcntl(fd, F_SETLK, struct flock*);

struct flock{

shortl_type;        //锁的类型:F_RDLCK, F_WRLCK,F_UNLCK

shortl_whence;        //计算锁的位置:SEEK_SET,SEEK_CUR, SEEK_END

off_tl_start;        //指定相对于whence的起始地址

off_tl_len;        //指定长度

off_tl_pid;        //锁定该区域的进程ID,如果使用F_SETLK,该成员无效

};

加锁之后,如果进程结束,或文件关闭,将会自动解锁

在某个区域加读锁后,其他进程可以继续加读锁,但不能加写锁

在某个区域加写锁后,其他进程不能加读锁,也不能加写锁

 

测试某个文件区域是否可以加指定的锁:

fcntl(fd, F_GETLK, struct flock*);

如果flock中的值发生改变,说明之前flock结构中设定的锁不可以

flock改变为:变成之前已有锁的数据,其中l_pid为之前加锁进程的ID

 

F_SETLK如果加锁失败,直接返回-1

F_SETLKW如果加锁失败,程序阻塞,直到加上锁为止

 

谁加的锁谁解锁,解锁方式F_SETLCK,传的flock结构中l_type为F_UNLCK

2.读取文件属性和状态

stat(file, struct stat*)

执行成功会把文件的所有属性和状态保存到stat结构体中

 

如果在一个保存了N个结构体信息,可用如下方式获取结构个数

stat(file, &st)

st.st_size/sizeof(Emp);

判断是目录还是文件

文件是不是目录,以及文件的权限,

文件的类型(目录,普通文件,字符设备文件,块设备文件,socket文件,管道文件),保存在st_mode中

 

S_ISDIR(st.st_mode);//判断是否为目录

S_ISREG(st.st_mode);//判断是否为普通文件

...

s.st_mode & S_IRUSR;//判断用户是否可读

s.st_mode & S_IFDIR;//判断是否为目录

 

fstat和stst一样,只不过第一个参数是文件描述符

lstat可以获取link文件的属性,

而stat和fstat获取的是link文件指向的文件属性

3.屏蔽字        

umask(屏蔽字);//返回之前的屏蔽字

若umask(0022);

则open("a.txt", O_RDWR | O_CREAT, 0666);所创建的文件权限为0644

4.truncate修改文件长度

5.文件映射

1)文件要打开,得到文件描述符

2)文件的大小需要调整,等于或大于你要映射的物理内存空间,防止总线错误

3)映射

mmap(NULL, size, PROT_READ | PROT_WRITE,

MAP_SHARED, fd, 文件偏移量);

4)将文件当内存使用

5)解除映射

munmap(p, size);

6.目录

mkdir

rmdir

chdir

DIR* opendir(const char*)

readdir//读取一个目录项(子目录,文件)

struct dirent {

long d_no;

char d_name[NAME_MAX+1];

}

struct dirent* p;

while(p=readdir(dir))//读取此目录中每一项,当读完时返回NULL


 

进程:

R 正在运行中的进程和可运行的进程

可运行的进程是指在等候队列中等CPU资源的进程

fork:

各种情况:

1)子进程先结束,子进程会给父进程发一个SIGCHLD信号,

父进程回收子进程相关资源

2)父进程结束,子进程成为孤儿进程,孤儿进程就会认init(pid=1)为父进程,

子进程结束时,发送SIGCHLD,init回收资源

3)子进程先结束,发送SIGCHLD给父进程,父进程由于某中原因没有收到,

则子进程资源没有回收,子进程变为僵尸进程

 

孤儿进程是正常情况,僵尸进程应该避免

 

fork之后会复制数据区,BBS段,堆,栈,文件描述符会共享代码区,文件表

 

signal:

忽略SIGTERM信号:

signal(SIGTERM, SIG_IGN);

恢复SIGTERM信号的默认行为:

signal(SIGTERM, SIG_DFL);

 

不能忽略SIGKILL,SIGSTOP信号,也不能捕获

 

作业:打印孪生素数

用一个进程产生素数,用另外一个进程找孪生素数

 

孪生素数:

两个数差2 , 3 5, 5 7


进程:

1)shell下执行一个命令,启动一个进程,这个进程就是shell进程的子进程

2)在一个进程用fork函数复制一份自己,复制出的那个进程就是这个进程的子进程,它还会在复制进程的位置开始运行

 这两个父子进程唯一的不同是fork函数的返回值不同,

 可以根据fork的返回值判断区别父子进程,从而让父子进程执行不同的代码段

  fork返回值

  >0  父进程 返回的是子进程的pid

  ==0 子进程

  <0  出错

 注:fork复制进程是在fork函数运行中复制的,即在函数返回前就已复制结束

3)vfork

和fork一样,创建一个进程,但不会进行复制,主要用于与exec函数搭配使用

还有一个特点:保证子进程先执行

 

进程关系:         

父进程先结束,子进程就会认pid==1(init)为父进程

子进程先结束,发送信号SIGCHLD给父进程,然后回收子进程资源

wait:

父进程等待子进程结束,如果子进程不结束,父进程阻塞

等待子进程的目的是为了获取子进程的返回值

 

获取返回值:

1)从wait中取得返回信息

  int status;

  wait(&status);

2)判断是否是正常返回(不是被强行中断)

  WIFEXITED(status);

3)取得返回值

  WEXITSTATUS(status);

若wait(NULL),表示不关心返回值,只是等待子进程结束

exec系列函数:

基本前缀:exec

可选后缀:

l : list参数为可变长参数

v : vector参数是字符串数组

p : PATH需要用到PATH环境变量中的地址中的程序来执行程序

e : environment传入环境变量

注意:

无论是l,还是v,最后必定要以(char*)0{NULL}结束

exec是将一个程序调入当前进程,覆盖当前进程的所有信息

信号:

进程级别的

信号是一个软件中断技术,信号会打断正常程序的执行

 

signal:

void (*signal(int, void (*)(int)))(int)

  ==typedef void(*sighandler_t)(int)

         sighandler_t signal(intsigno, sighandler_t handler)

功能类似于:在当前进程上,注册渔歌指定的信号的处理函数,当收到相应的信号时则调用处理函数进程响应处理


sleep函数在休眠期间如果接受到信号被打断,当处理函数返回时不会继续休眠


1.信号

进程可以接受,处理信号

1)信号处理函数,可以处理多个信号,也可以只处理一个

2)首先要先捕获信号

signal函数                简单信号处理,很多动作不能选择

sigaction函数        对信号进行更加精细的处理,有多种选项

sigaction结构 {

sa_handler;//信号处理函数

sa_sigaction;//信号处理函数,当有SA_SIGINFO选项时有效

sa_mask;//要屏蔽的信号

sa_flags;//选项

}

注意:sa_handler和sa_sigaction的实现很可能使用同意存储区,

所以二者只能有其一

3)处理信号 信号会中断正常程序的执行,不可重入函数的调用需注意

1.在正常程序中正在访问全局(静态)变量,而信号处理函数也访问这些变量,

  可能出问题

2.正在执行一些系统调用(malloc, free...)在信号函数中也做同样的事,

  可能出问题

注意:信号处理函数尽量简单,一般都是处理一些错误,或者处理高优先级的突发情况.

进程处理的信号越多,问题越复杂

4)发送信号

1.键盘按键

Ctrl+C        SIGINT

Ctrl+\        SIGQUIT

Ctrl+Z        SIGSTOP

...

2.系统错误 系统默认处理一般为终止或忽略

3.shell命令

kill -信号 进程pid

4.进程中发信号

kill函数 kill(进程pid, 信号)

pid>0        给指定进程发

pid==0        给同进程组发

pid==-1        给所有有权限的进程发

pid<-1        给pid的绝对值进程组发

5.alarm

定时发信号SIGALRM

函数不是阻塞函数

如果上一次alarm时间未到,又再次alarm,

则上次alarm取消,返回上回alarm剩余时间

alarm(0)取消定时

6)pause

暂停并等待信号,直到收到一个信号被打断

2.信号集

表示多个信号的变量

3.信号屏蔽

进程在特定阶段,不希望有某些信号打断,可以屏蔽这些信号

1)signal信号处理函数,默认的屏蔽掉同类型的信号

2)sigprocmask函数专门用来屏蔽信号

sigset_t set, oset;

sigfillset(&set);

sigprocmask(SIG_SETMASK, &set, &oset);//屏蔽掉所有信号

...//一段不允许任何信号打断的代码

sigprocmask(SIG_SETMASK, &oset, NULL);/恢复原来的屏蔽字

3)sigpending 获取被屏蔽掉的信号

4.进程通信

InterProcess Comunication(IPC)

1)用普通文件 fork

2)管道通信: 有名管道,无名管道, 管道文件是有序文件

3)通过内存映射方式通信

4)用队列方式通信

5)socket文件

6)网络通信

 

 

1) 管道

2) FIFO

3) 内存映射

4) Socket

XSI IPC:

1) 消息队列

2) 信号量

3) 共享存储器(最快)

 


IPC:

管道: 有名(任意两个进程) 无名(父子进程之间)

无名管道:

父进程: int fd[2];

pipe(fd);//创建一个无名管道,两个文件描述符

//fd[0] : 专用于读

//fd[1] : 专用于写

fork子进程

父进程和子进程分别关闭读端或写端(读写个留一个)

父子进程之间开始通信

父子进程分别关闭剩下的读写端

XSI IPC:

共享内存: 消息队列 信号量

共同的特点:

系统内核都会维护IPC结构,进程可以创建IPC结构,创建时需要一个key

产生key的方式:

1)IPC_PRIVATE(默认创建新的IPC结构)

2)在共享的头文件中定义一个key

3)事先给定一个路径和项目号

key_t key = ftok("pathname", num);

shmid_ds        msgid_ds        semid_ds

编程模型:

1)获得key

2)创建IPC结构(获取IPC结构) shmget msgget semget

3)通信的准备工作shmat

4)通信 读写内存 msgsnd msgrcv

5)释放资源 shmdt

6)删除 shmctl msgctl semctl : IPC_RMID

共享内存:

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

cmd:

IPC_STAT : 取得shmid_ds结构的信息

IPC_SET  :设置操作系统中的shmid_ds,只允许设置权限信息

IPC_RMID : 删除系统中的shmid_ds

shmid_ds:

struct shmid_ds {

struct ipc_perm shm_perm;

size_t                        shm_segsz;//内存段大小

pid_t                        shm_cpid;//创建进程pid

pid_t                        shm_lpid;//最后挂接或脱接的pid

shmatt_t                shm_nattch;//挂接在内存段上的进程数

};

特点:

效率高,但存在同步问题

消息队列:

内核维护的一个链表实现的队列(FIFO)

创建: msgget(key_t, IPC_CREAT | IPC_EXCL | 0600);

返回队列的id

获取id: msgget(key_t, 0);

发送消息:

msgsnd(int msgid, const void* msgp, size_t msgsz, int msgflg);

msgp: 有类型的消息, 无类型的消息

size: 有类型的消息,大小不包括类型本身的长度

  无类型的消息,大小就是消息长度

flag: 一般用默认0,阻塞 若设置为IPC_NOWAIT则不阻塞

接受消息:

msgrcv(int msqid,void* msgp,size_t msgsz,long msgtype,intmsgflg);

msgtype==0 : 不判断类型

msgtype>0  :接收消息时要判断类型                

msgtype<0  :取小于等于type的绝对值的类型最小的消息(从小到大)

msgflag:

MSG_NOERROR : 如果消息比设定的大小还大,则截断消息,而不出错.

  默认出错,消息还留在队列中

    IPC_NOWAIT  : 如果队列中没有要取的消息,不需要等待,直接返回。

                             默认是阻塞



线程:

每个线程除拥有自己独立的栈空间、线程ID、一组寄存器值、调度优先级和策略、信号屏蔽字、error变量、线程私有数据外,

其他都是共享的(堆区,全局区,代码区,文件描述符)

 

进程ID在整个系统中是唯一的,但线程ID不同,线程ID只在它所属的进程环境中有效(与Windows不同)

三种基本的同步机制:互斥量、读写锁(共享-独占锁)、条件变量(还有信号量)

pthread_join(pthread_t tid, void** value_ptr):

1.让主线程阻塞,直到对应的线程运行结束返回

2.取得线程返回值的地址保存在*value_ptr中

 

 


Socket套接字

文件

网络

 

网络编程:

数据报UPD        一对一

无连接 不安全,可能会丢失数据,数据无序,效率高

数据流TCP        C/S

有连接 安全,不会丢失数据,数据有序,效率低

 

 

 

死锁:

产生死锁的原因:

1) 竞争资源

2) 进程间推进顺序非法(请求和释放资源顺序不当)

解决死锁的基本方法:

1) 预防死锁

银行家算法

2) 避免死锁

3) 检测死锁

4) 解除死锁

5) 鸵鸟策略

死锁的4个必要条件

互斥、请求保持、不可剥夺、环路

 

操作系统中进程调度策略有哪几种?

FCFS(先来先服务),优先级,时间片轮转,多级反馈

 

 

线程是指进程内的一个执行单元,也是进程内的可调度实体.

与进程的区别:

(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位

(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行

(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.

(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

 

 

列举几种进程的同步机制,并比较其优缺点。

在主流的Linux内核中包含了几乎所有现代的操作系统具有的同步机制,这些同步机制包括:原子操作、信号量(semaphore)、读写信号量(rw_semaphore)、spinlock、BKL(BigKernel Lock)、 rwlock、 brlock

(只包含在2.4内核中)、RCU(只包含在2.6内核中)和seqlock(只包含在2.6内核中)

你可能感兴趣的:(UnixC)