突然看到以前整理的资料,拿来给大家分享一下。包括C、系统编程、网络编程、BS等....你要是应届毕业生若是会这些,基本上哪家公司都会抢着要你。
<一> c语言整理资料
大学期间主要是针对windows开发,windows 开发的特点:在电脑上安装一个vc 6.0 编辑程序->编译程序->看现象
->若有问题就修改、调试程序(安装xshell:可以使用户在windows平台*问linux主机、notepad++)
在培训期间主要是应用linux 开发:linux下的程序开发就是在本地电脑上安装虚拟机(VMware player)操作系统为ubuntu(安装samba、ssh)
将vmtools的iso镜像放在虚拟机的光驱,以便windows、linuxs信息的交换
samba服务器在两种不同的操作系统间架起一座桥梁,使linux系统和windows系统之间可以通信
安装系统的命令:sudo apt-get install......
一、标准输入函数:scanf(不要出现\n等转义字符)
标准输出函数:printf(往终端上打印结果)
二、常用的linux命令
1、man: man ls;man 3 printf
1、standard commands (标准命令) 2、system calls(系统调用,open)
3、library functions(库函数,printf)
2、ls tree clear
3、cd ..进入上一层目录 ; cd -进入上一个进入的目录 ; cd ~家目录
4、pwd 显示当前工作目录的绝对路径
5、mkdir 创建目录
-p 递归地在指定路径建立文件夹
6、touch 创建文件
7、cat 显示文本文件内容
8、rm 删除文件/目录 rm a -rf
-r 删除文件夹时必须加此参数
-f 强制删除文件
9、cp
-R拷贝文件夹
-v 显示拷贝进度
10、mv 移动文件
11、tar:压缩、解压
gzip格式:
压缩 tar zcvf 压缩包的名字 文件1 文件2;tar zcvf bk *.c
解压 tar zxvf 压缩包名
bz2格式:
压缩 tar jcvf .......
解压 tar jxvf....
12、编辑器(vi+gedit)
编译器(gcc)
预编译.i 编译.s 汇编.o 链接
调试器(gdb)
gcc -g sum.c -o sum
gdb sum
输入命令(l 列出程序清单
r 运行程序
b[行号\函数名]
info b 查看断点信息
clear [行号]清除断点)
三、C的关键字共32个
1、格式化输出字符
%p 指针的值 %x 十六进制 %o 八进制
%d **6**
%3d ** 6**
%03d **006**
%-3d **6 **
%5.2f 共占5位,小数点后占2位,.也占一位
%.2f 小数点后占2位
2、typedef 给一个已有的类型重新起一个名字
unsigned short int a=125;
=>typedef unsigned short int U16;
U16 a=125;
typedef 作用,给已有的类型起个新名字,没有创造新的类型。
起名可以分三步走,前两步在草稿纸做,最后一步是咱们想要的。
1:用想起名的类型,定义一个变量。
unsigned char a;
2:新的类型名替代变量名
unsigned char U8;
3:在最前面加typedef
typedef unsigned char U8;
以后就可以用U8 定义变量了
U8 a;
unsigned char a;
效果是一样的
3、自动转换的原则(字节数少的向字节数大的类型转化)
char/short->signed int->unsigned int->long->double
^
float
4、long与int有何不同??
平台不同int型占的位数不同,处理器若为16位,int就占2个字节;
处理器若为32位,int就占4个字节;而long型不管在哪种平台一定占4个字节
5、强制类型转化(类型说明符)(表达式)例:(int)(x+y)
6、运算符
条件运算符(条件?表达式1:表达式2) 条件为真执行表达式1,为假执行表达式2
逗号运算符(,)
num=(2,6)===>num=6为逗号后的值
复合赋值运算符
自增自减运算符
++i;(先加,后用)
移位运算
左移<< :高位溢出,低位补零
算术右移:低位溢出,高位补符号位
逻辑右移:低位溢出,高位补0
四、程序的结构
顺序结构
选择结构
1)if else
2)switch(表达式)
{
case 常量表达式1:
语句1;
break;
.
.
default: 语句3;break;
}
若case1中无break 则不再判断,直接执行case 2
循环结构
1)for(;;) {}
2)while()
3)do while()
4)goto
break用于跳出本层循环;continue用于结束本次循环,进入下次循环
五、数组:相同类型若干变量的集合
1、全局数组若不初始化,编译器将其初始化为0;
局部数组若不初始化,内容为随机。
2、数组初始化时,行下标可以省略,列下标不可以省略
3、用字符串方式赋值比用字符逐个赋值要多占1个字节,用于存放字符串结束标志‘\0’
六、gets(str) 、scanf("%s",str)
功能:从键盘获取一个字符串
gets允许输入的字符串含有空格,而scanf不允许有空格
七、函数:库函数、自定义函数(形参、实参)
1、在函数被调之前,形参不占内存
2、函数声明的原因??
C语言编译系统是从上往下编译源文件的。
1)主函数和被调函数在同一个源程序中
a、主上被下
将被调用函数的第一行复制上去,加分号;
b、主下被上 不需要声明
2)不在同一源程序 声明:extern .....
八、宏定义:用宏名来表示一个信息
在预编译时会“宏展开” ,宏只在宏定义的文件中起作用。
无参数的宏定义:
#define 宏名 字符串 宏名为大写,字符串可以是常数、表达式等 ;不加分号;#undef 终止宏定义的作用域
#define IP 3.1415926
带参数的宏
格式:#define 宏名(形参表) 字符串
调用:宏名(实参表)
宏展开:宏替换
#define S(a,b) a*b
area=S(3,2); //area=3*2;
九、条件编译
测试存在:
#ifdef 标识符
程序1;
#else
程序2;
#endif
1、防止头文件被重复包含
#ifndef __LCD_H__
#define __LCD_H__
#endif
十、打印行号
printf("%d\n",__LINE__);
printf("%d\n",__FILE__);//宏所在的文件名
printf("%d\n",__DATE__);//编译的日期
printf("%d\n",__TIME__);//编译的时间
直接gcc a.c 会生成a.out(也是一个可执行文件)
十一、静态库、动态库的制作与使用
链接分为:静态链接、动态链接
静态链接:由链接器在链接时将库的内容加入可执行程序中
优点:对环境的依赖性较小
缺点:生成的程序比较大,需要更多的系统资源,装入内存使耗时多
动态链接:连接器在链接时仅仅 建立与所需库函数的之间的链接 关系,在程序运行时才将所需资源调入可执行程序
优点:在需要时才会调入对应的资源函数,,较小的程序体积
缺点:依赖动态库,不能独立运行
当静态库与动态库重名时,系统会优先连接动态库
gcc -c mylib.c -o mylib.o//编译目标文件
ar rc libtestlib.a mylib.o//制作静态库
十二、 字符串
1、输出函数puts
格式:puts(字符数组名)
功能:把字符数组中的字符串输出到显示器
2、字符串输入函数gets(字符数组名)
格式:gets(字符数组名)
功能:从标准输入设备键盘输入一个字符串
注意:gets函数并不以空格为结束标志,而是以回车为输入标志。scanf对空格和回车都认为
是一个字符串的结束标志
3、字符串连接函数strcat/strncat
char *strcat(char *str1,char* str2);
功能:将str2连接到str1后面(str1后的'\0'会删除,str2后的'\0'会保留)
返回值:返回str1字符串的首地址
char *strncat(char *str1,char* str2,int num);
功能:将str2的前num个字符连接到str1后面(str1后的'\0'会删除,str2后的'\0'会保留)
4、strlen
功能:返回字符串的实际长度,不含'\0'
sizeof与strlen 不同
5、strcpy('\0'会拷贝过去)/strncpy('\0'不会拷贝过去)
char *strcpy(char *dest,const char *src);
6、strcmp、strncmp
int strcmp(char *str1,char *str2);
功能:比较str1和str2的大小
返回值:相等0; str1 大于str2返回>0; str1 小于str2返回<0
7、memset
void *memset(void *str,char c,int n)
功能:将str所指向的内存区的前n个全部用c填充 常用于清除指定内存memset(str,0,sizeof(str))
8、atoi/atol/atof
int atoi(const char *str)
功能:将str所指向的 数字字符串 转化为int/long/double
9、strchr(******不具有记忆功能,下次查询还是从头开始查)
char* strchr(char *str1,char ch)
功能:在字符串str中查找字母ch出现的位置
返回值:返回第一次出现的ch的地址;若找不到就返回NULL
strstr
char *strstr(char *str1,char *str2)//查找字符串
10、strtok字符串切割函数(具有记忆功能)
char *strtok(char s[],const char *delim) //s指向预分割的字符串,delimw为分割字符串中包含的所有字符
在第一次调用时:strtok()必须给与参数s,,往后的带调用则将s参数制成NULL
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[]="abcd*ef*ghijk!milll.kkkk*ddddd";
char str2[]="*!";
char *result[10];
int num=0,i;
result[num]=strtok(str1,str2);//第一次调用,第一个参数为str1
while(result[num]!=NULL)
{
num++;
result[num]=strtok(NULL,str2);//第二次调用及以后调用,第一个参数为NULL
}
printf("num=%d\n",num);
for(i=0;i<num;i++)
{
printf("result[%d]=%s\n",i,result[i]);
}
}结果:num=5
result[0]=abcd
result[1]=ef
result[2]=ghijk
result[3]=milll.kkkk
result[4]=ddddd
11、sprintf、sscanf
sprintf(buf,"%d:%d:%d",2013,2,3);//输出到buf指定的内存区域*******组包
sscanf("2013:2:3","%d:%d:%d",&a,&b,&c)//从buf指定的内存区域中读入消息*******拆包
在sscanf中:1、跳过* 2、读到指定宽度的数据 %4s 3、%[^a]遇到a但不要a 4、%[a-z]要a-z之间的字符
十三、结构体、共用体、枚举
1、结构体(将不同的结构体组合成一个有机整体)
最常用的2种结构体形式:
1)struct stu{
成员列表;
};
struct stu Lucy;
2)struct stu{
成员列表
}Lucy;
***** 3)typedef struct student{
成员列表
}STU;
STU Lucy;
2、结构体数组、结构体指针
3种等价形式
1)lucy.num=100;
2)p->num=100;
3)(*p).num=100;
3、结构体的内存分配
2个规则:1)以多少字节开辟空间。2)字节对齐
4、共用体
typedef union data{
成员列表;
}DATA;
特点:1、共用体内可以有多种类型不同的成员,但每一瞬间只能有一种起作用
2、共用体中起作用的变量是最后一次存放的成员
3、共用体变量的地址和成员的地址是同一地址
#include <stdio.h>
union xx{
long int x;
char y;
int z;
}a={10};
int main()
{
a.x=5;
a.y=6;
a.z=15;
printf("%ld **%d** %d\n",a.x,a.y,a.z);
printf("%ld\n",a.x+a.y);
}
结果15 **15** 15
30
#include <stdio.h>
typedef union data {
char a;
short int b;
float c;
}DATA;
int main(int argc, char *argv[])
{
DATA temp;
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
printf("%p\n",&(temp.c));
return 0;
}
结果: 若将union换成struct 结果为:8
0xbfcde348
0xbfcde348
0xbfcde34a
0xbfcde34c
4
0xbfa14e8c
0xbfa14e8c
0xbfa14e8c
0xbfa14e8c
5、枚举
将变量的值一一列举出来
enum 枚举类型week
{
枚举值表
};
enum week workday,weekday;
**枚举值是常量。不能在程序中在对其复制
枚举元素本身由系统定义了一个表示序号的数值,从0开始排序0 1 2 3 4....
也可以自己定义 3 4 5....
十四、链表
链表有一系列的节点组成(每个节点一般为结构体形式),节点在运行时动态生成(malloc)
节点分为两部分:数据域、指针域
例:typedef struct student{
int num;
float score;
struct student *next;
}STU;
1、链表的创建
添加节点到链表到链表的尾部:1)当链表为空时,将链表的头直接指向待添加的新节点
2)链表不为空时,首先遍历链表找到链表的尾节点,然后将待添加的新节点挂在尾节点上
STU *head,*new;
new=(STU *)malloc(sizeof(STU));
void link_create(STU **head,STU *new)
{
//先定义一个中间变量,来保证头文件不变
STU *p_mov=*head;
if(*head==NULL)//插入第一个节点
{
*head=new;
new->next=NULL;
}
else
{
while(p_mov->next!=NULL)//找到尾节点
{
p_mov=p_mov->next;
}
p_mov->next=new;//将新节点添加到尾节点
new->next=NULL;
}
}
添加节点到链表到链表的头部
void link_create(STU **head,STU *new)
{
if(*head==NULL)//插入第一个节点
{
*head=new;
new->next=NULL;
}
else
{
new->next=*head;
*head=new->next;
}
}
2、链表的遍历 :遍历输出链表所有节点
1)得到链表第一个节点的地址,即头结点 2)设一个临时指针变量p_mov,指向第一个节点head,即
获得p_mov所指节点的信息 3)使p_mov指向下一个节点
void link_print(STU *p_head)
{
STU *p_mov;
p_mov=p_head;
while(p_mov!=NULL)
{
printf("num=%d name= %s score=%d\n",p_mov->num,p_mov->name,p_mov->score);
p_mov=p_mov->next;
}
}
3、查找关键字:按照指定关键字查找所需节点
1)得到链表的第一个节点的地址,即头结点 2)设一个临时指针变量p_mov,指向第一个节点head,即
获得p_mov所指节点的信息 3)比较是否是要查找的节点
STU *link_search_name(STU *head,char *name)
{
STU *p_mov;
p_mov=p_head;
while(p_mov!=NULL)
{
if(strcmp(p_mov->name,name)==0)
{
return p_mov;
}
else
p_mov=p_mov->next;
}
return NULL;
}
4、释放链表(从头节点一个一个的删除节点)
1)释放节点前要先保存下一节点,即将下一节点的地址赋值给p_mov
STU * link_free(STU **head)
{
STU *p_mov;
p_mov=*head;
while(*head!=NULL)
{
p_mov=*head;
*head=*head->next;
free(p_mov);
}
return NULL;
}
5、删除节点
1)删除的是第一个节点,只需使head指向下一个节点
2)删除的不是第一个节点,使被删除节点的前个节点指向后一个节点
void link_delete_num(STU **p_head,int num)
{
//先定义2个指针,用来保存上一个节点和下一个节点,即该节点的上一节点的next指向该节点的
下一节点
STU *pb,pf;
pb=pf=*p_head;
if(*p_head==NULL)
{
printf("该链表为空,没有要删除的节点\n");
return ;
}
while(pb->num!=num && pb->next!=NULL)
{
pf=pb;
pb=pb->next;
}
if(pb->num==num)
{
if(pb==*p_head)
{
*p_head=pb->next;
}
else
{
pf->next=pb->next;
}
free(pb);
}
else
{
printf("没有您要删除的节点\n");
}
}
6、插入节点
1、链表为空时,插入节点即为头结点 2、查找3、找到(插在头或插在一般位置)或者找不到(查到最后)
void link_insert_num(STU** head,STU* new)
{
STU *pb,*pf;
pb=pf=*head;
if(*head==NULL)
{
*head=new;
new->next=NULL;
}
while((new->num<=pb->num)&&(pb->next)!=NULL)
{
pf=pb;
pb=pb->next;
}
if(new->num >pb->num)
{
if(pb==*head)
{
new->next=*head;
*head=new;
}
else
{
pf->next=new;
new->next=pb;
}
}
else
{
pb->next=new;
new->next=NULL;
}
}
7、排序
void link_order_num(STU **head)
{
STU *pb,*pf,temp;
pb=pf=head;
if(head==NULL)
{
printf("链表为空\n");
return 0;
}
while(pf->next!=NULL)
{
pb=pf->next;
while(pb!=NULL)
{
if(pb->num < pf->num)
{
temp=*pf;
*pf=*pb;
*pb=temp;
temp->next=pf->next;
pf->next=pb->next;
pb->next=pf->next;
}
pb=pb->next;
}
pf=pf->next;
}
}
8、逆序
STU *link_rever_num(STU *head)
{
STU *pb,*pf,*r;
pb=pf=head;
if(head==NULL)
{
printf("链表为空\n");
return 0;
}
if(head->next==NULL)
{
printf("链表只有一个节点,不用逆序\n");
return 0;
}
pb=pf->next;
while(pb!=NULL)
{
r = pb->next;
pb->next=pf;
pf=pb;
pb=r;
}
head->next=NULL;
head=pf;
return 0;
}
十五、文件 **** 一个汉字占2个字节(UTF-16编码),一个英文字母占1个字节
1、从用户的角度讲,文件分为普通文件和设备文件
普通文件是指驻留在磁盘或其它外部介质的一个有序数据集
设备文件是指与主机相连的各种外部设备,如显示器(标准输出文件)、打印机、键盘(标准输入文件)
2、从文件的编码方式上看,分为:文本文件(ASCII码文件)和二进制文件
ASCII码文件在磁盘中存放时每个字符对应一个字节:如
ASCII 00110101 001101110 00110111 00111000
十进制 5 6 7 8
3、文件指针(一个指针变量指向一个文件),通过文件指针就可以对所指文件进行各种读写操作
FILE *fp (fp为指针变量标识符)
4、打开文件
FILE *fp=NULL;
fp=fopen("a.txt","r+");*********若打开失败返回NULL
fclose(fp);******成功返回0,失败返回非零
12种打开方式:
“rt”
只读打开一个文本文件,只允许读数据
“wt”
只写打开或建立一个文本文件,只允许写数据
“at”
追加打开一个文本文件,并在文件末尾写数据
“rb”
只读打开一个二进制文件,只允许读数据
“wb”
只写打开或建立一个二进制文件,只允许写数据
“ab”
追加打开一个二进制文件,并在文件末尾写数据
“rt+”
读写打开一个文本文件,允许读和写
“wt+”
读写打开或建立一个文本文件,允许读写
“at+”
读写打开一个文本文件,允许读,或在文件末追加数据
“rb+”
读写打开一个二进制文件,允许读和写
“wb+”
读写打开或建立一个二进制文件,允许读和写
“ab+”
读写打开一个二进制文件,允许读,或在文件末追加数据
*****r(read) 要求文件必须存在;w(write);a(append)追加 要求文件一定存在;t(text)文本文件;b(banary)二进制文件;+读和写
5、读出、写入
(每个文件末有一结束标志EOF)
1、读一个字节(字符):ch=fgetc(fp); /*文本文件中读到结尾返回EOF*/fp中读取一个字符并送入ch中
写一个字节 :fputc(ch,fp);//成功则返回输出的字节,失败返回EOF=-1
2、读一个字符串:fgets(str,n,fp);//从fp指向的文件读出n-1个字符,在这之前若遇到‘\n’或EOF会立刻结束,在最后
添加一个'\0'
写入一个字符串:fputs(str,fp);
3、数据块的读写:返回值是实际读写的数据块数
fread(buff,size,count,fp);
fwrite(buff,size,count,fp);
4、格式化读写函数
fscanf(文件指针,格式字符串,输入表列);
fprintf(文件指针,格式字符串,输出表列);
例如:
fscanf(fp,"%d%s",&i,s);//拆包
fprintf(fp,"%d%c",j,ch);//zubao
5、文件定位
**** 1)rewind函数
void rewind(fp);
功能:把文件内部的位置指针移到文件首
2)ftell函数
long ftell(fp);
功能:获取文件流 目前的读写位置
返回值:返回当前位置距文件头的字节数;出错返回-1
3)fseek函数
int fseek(fp,位移量,起始点);
功能:移动文件流的读写位置
参数:“位移量”表示移动的字节数,要求位移量是long型数据,以便在文件长度大于64KB 时不会出错。
当用常量表示位移量时,要求加后缀“L”。
“起始点”表示从何处开始计算位移量,规定的起始点有三种:文件首,当前位置和文件尾。
文件首 SEEK_SET 0
当前位置 SEEK_CUR 1
文件末尾 SEEK_END 2
例如:
fseek(fp,100L,0);
其意义是把位置指针移到离文件首100个字节处。
十六、make
1、用make 的优点:
1)在大项目中源代码比较多(很多个.c ),手工编译的时间长而且编译命令复杂,难以记忆和维护
2)在改动其中一个文件时,能判断哪些文件被修改,可以只针对文件进行重新编译,然后重新连接所有的目标文件,
节省编译时间
2、makefile
$引用变量 @运行时不显示命令 #屏蔽命令
$^依赖文件列表中除去重复文件的部分、
$<依赖文件列表中的第一个文件
$@ 目标名
1)自定义变量语法
定义变量 变量名=变量值
引用变量 $(变量名)
2)make工具传给makefile的变量 make cc=arm-linux-gcc
补充内容:
1、内存的分配
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
系统自动分配
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
需要程序员自己申请
3、全局区(静态区)(static)— 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
4、文字常量区 — 常量字符串就是放在这里的,程序结束后由系统释放 。
5、程序代码区 — 存放函数体的二进制代码。
2、堆和栈
-----------------------------------------------------
<二> 驱动文档
s5pv210是三星出的一款处理器,主要是用于智能手机和平板电脑
1、bootloader原理?
bootloader:启动引导程序;就是系统加电后,在操作系统内核或用户应用程序运行之前运行的一段或多段程序
功能:将系统的软件硬件环境带到一个合适的状态,为调用操作系统内核准备好正确的环境
嵌入式系统开发流程::::
1.硬件开发
2.启动加载程序(bootloader)
3.内核(kernel)
4.根文件系统(root)
5.设备驱动(driver)
6.应用程序(线程、进程)
bootloader是启动引导程序,它的作用是加载操作系统
bootloader的启动过程?
第一步:cpu启动起来后,先来到0x00000000这个地址处(也就是irom的地址),此时会看到有一段代码(这是irom中固定的代码)在这里,这段代码会找到bootloader的第一段代码(以下就称为BL1)存放的地址,由于BL1存放在nand flash中,不支持片上执行,所以irom会把这段代码拷贝到iram中,
但是在拷贝之前,irom会先把iram的起始4个地址分别写上一定的内容(不过通常只写0x00地址为BL1所占空间的大小(一般是8k),而其他三个地址全写为0),把BL1这段代码拷贝到iram后,irom的使命就快要完成了,irom的最后一个任务就是把pc指针指向放BL1的那块地址的起始位置,然后就按照这个地址的指令开始执行。
第二步:这个时候开始执行BL1的代码。这段代码的功能是初始化硬件,比如串口,内存,显示器,按键等等。
在初始化所有需要初始化的硬件后,BL1还会有一个拷贝指令,就是要把bootloader的第二段代码拷贝到sdram中
这时候BL1的使命也将要完成,于是,BL1会把pc指针跳转到BL2的地址,此时所有要运行的代码都在sdram中,这次的跳转不会直接从这次拷贝的开始地址执行,而是跳过BL1代码所占的地址,从BL2开始执行。
第三步:执行BL2中的代码。此时BL2代码的功能主要是实现MTD设备驱动初始化,电源、时钟初始化,堆栈空间,以及各种必要的初始化,并且会提供一个命令行,可以进行交互。在这之后会有一个设置内核参数的过程,这些参数在内存中的存在方式也是以结构体存储,以链表进行关联的,而这个链表有一个固定的起始地址-0x3000_0100;每一个结构体代表一个信息,并首尾相连,内核在需要这些参数时,就可以再对应的地址上取数据。
这一步执行完毕后,就要把kernel的代码拷贝到sdram中的一个指定地址,并且会把这个地址强制转换成一个函数指针,并且向这个函数中传递一些参数,最终会到内核中执行内核代码。这个时候,内核就会被引导到执行状态。
2、为什么要配置编译?
不同的芯片的板级信息不同,配置本芯片的信息
3、
总线:各部件之间传递信息的公共通道
采用总线的好处:*资源:platform机制将设备的资源单独注册进内核,由内核进行统一管理;
**驱动:使用这些资源时提供的标准接口对资源进行申请并使用,实现驱动与资源的分离与统一
platform总线:连接设备和驱动的桥梁,把device资源(代表物理设备)和driver(驱动程序)联系在一起
input子系统:input子系统是字符设备驱动的另一种封装;是输入设备驱动一个标准,一个约定俗称的规范,几乎所有输入设备驱动都是使用iput来上报输入信息的
I2C总线协议:两线式半双工同步串行总线,接口较少,通信的效率较高,,一条总线上可以有多个主机,但同一时刻只允许一个主机工作
---------------------------------------------------------
<三> 指针
一、存储器
存放数据的器件。
1、内存
内部存储器.常见的内存有:ram、DDRII
存储的数据掉电丢失。暂存数据
1:物理内存
实实在在看的见,摸得着的内存器件。
存储空间不是特别大,一般在几个G以下。
2:虚拟内存
操作系统虚拟出来的内存。
咱们写应用程序的时候,在程序中看到的内存都是虚拟内存。
操作系统会在虚拟内存和物理内存之间做一个映射。
在32位系统下,咱们每个运行的程序都会分配内存,每个进程,都有
4G的虚拟内存。
3:内存编号
每个进程都有4G字节的寻址空间。
咱们可以将进程的内存看成4G个字节的字符数组。存储单元是一个字节
对4G大小的内存每一个存储单元进行编号
0x 00 00 00 00 ~ 0x ff ff ff ff
总结一句话:这个所谓的内存编号,就是地址
2、外存
外部存储器,常见的外存有:rom、flash、硬盘、u盘、sd卡、光盘、磁带
存储的数据掉电不丢失,长期存储数据
内存的分区
1.堆
在动态申请内存的时候,在堆里开辟内存。
2.栈
主要存放局部变量。
3.静态全局区
1:未初始化的静态全局区
静态变量(定义变量的时候,前面加static修饰),或全局变量 ,没有初始化的,存在此区
2:初始化的静态全局区
全局变量、静态变量,赋过初值的,存放在此区
4.代码区
存放咱们的程序代码
5.文字常量区
存放常量的。
二:指针的概念 ---指针和指针变量
1、指针
指针就是地址,其实就是存储单元的编号。
2、指针变量
指针变量就是个变量,用来存放地址编号的变量。
1:指针变量的定义方法:
数据类型 * 指针变量名;
char *p;//定义了一个字符指针变量,只能存放字符变量的地址
变量名是 p而不是*p, 在定义指针变量的时候,这个* 代表修饰
的意思,说明p是个指针变量。
大家都知道,在32系统下,地址编号都是4个字节的,所以
在32系统环境下,所有的指针变量本身都占4个字节。
char ch;
p=&ch;//用字符变量ch的地址,给p赋值,变量p里存放的是ch的地址。
咱们也可以说p指向了ch。
---------------------
int *p;//整型的指针
int a;
p=&a;//p存放了变量a的地址编号。
-------------------------------
short int *p;
short int a;
p=&a;
-----------------------
long int *p;
long int a;
p=&a;
--------------------------
float *p;
float a;
p=&a;
--------------------------
double *p;
double a;
p=&a;
注意p占4个字节。
对应类型的指针变量,只能存放对应类型变量的地址。
char *p;//字符指针,只能存放字符型数据的地址
short int *p;//短整型的指针,只能存放短整型数据的地址。
其他类型的亦然
总结:
指针的类型非常多,但是指针变量有共同的特点,
1:存放地址编号的变量
2:32位系统环境下,所有类型的指针变量都占4个字节。
指针的类型:
char *p;
short int *p;
int *p;
long int *p;
float *p;
double *p;
--------------------
struct stu *p;//结构体指针
int **p;//指针的指针
int(*p)[5]//数组指针
void (*p)(void);//函数指针
3、取值运算符: *
*+地址 等价于 指针指向的变量
int *p;
int a=100;
p=&a;
*p//等价于a
注意:* + 地址 取值的时候到底取几个字节,取决于 指针的类型
*+字符指针 取1个字节
*+ 整型的指针 取4个字节
4、字符串和指针
1:字符数组:
char string[]="hello world";
字符数组,是多个字符变量的集合,可以存放多个字符
hello world存放在数组string里
c语言规定:数组的名字代表一个数组,是数组的首地址,
即数组第0个元素(string[0])的地址,是个常量
不能 用等号(赋值号)给数组的名字赋值。
要想把字符串写到数组里,要用strcpy进行拷贝
strcpy(string,"hello kitty");
2:指针字符串
char *str="hello world";
注意:str是个字符指针变量,只能存放地址编号,不能存字符。
所以:hello world存放在文字常量区,str存放了字符串(h)的首地址
因为str是变量,所以可以用赋值号给str赋值
str="hello kitty";//是可以
相当于 str不再保存hello world的地址了,保存hello kitty的地址。
即:str指向了 hello kitty;
注意:
strcpy(str,"hello kitty");//出错,会导致内存污染
文字常量区的内容不允许修改
3:堆中的字符串
char *str;
str=(char *)malloc(100);
赋值1:
strcpy(str,"hello world");
//相当于将hello world填到申请的内存里了。
赋值2:
str="hello world";
这种赋值方法,是改变str的指向,并没有将hello world 存放在申请的空间里。
注:
char *str;
str=(char *)malloc(100);
strcpy(str,"hello world");
//相当于将hello world填到申请的内存里了。
如果我想用hello kitty 替代申请内存里的hello world
只能用 strcpy(str,"hello kitty");
4:可修改性
1.数组中的内容是可以修改
char string[100]="i love c";
string[0]='Y';//是允许的
2.堆中的内容也是可以修改的
char *str;
str=(char *)malloc(100);
strcpy(str,"I love C");
str[0]='Y';//是允许的,因为堆里的内容可修改
*str='Y';
3.文字常量区的内容是不可修改的
char *str="I love C";
str[0]='Y';//是错误的,因为文字常量的内容是不允许修改的。
指针指向的内存到底能不能被修改,主要看指针指向哪里!
三:指针和数组
1、指针指向数组的元素
指针可以保存变量的地址,数组的元素也是变量,也存放在内存中,也有
地址,所以指针可以保存数组元素的地址,即指向数组的元素。
int a[10];//定义了一个整型数组
int *p;
p=&a[3];
p=a;//数组名字就是数组的首地址,是数组第0个元素的地址
相当于p=&a[0];
其他类型的数组也是类似的。
指针的运算:
1:指针可以加一个整数
当一个指针指向数组的时候,加一个整数有意义。
例1:
int a[10];
int *p;
p=a;
p=p+3;//即往下指3个元素即 a[3]的地址,给p赋值。
2:指针可以比较大小
只有两个指针指向同一个数组的元素的时候,比较大小才有意义
例1:
int a[10];
int *p,*q;
p=&a[2];
q=&a[5];
指向前面元素的指针,小于指向后面元素的指针。
此例中p小于q
3:指针和指针可以做减法
只有在两个指针指向同一个数组的元素的时候,两个指针做减法才有意义
例1:
int a[10];
int *p,*q;
p=&a[0];
q=&a[3];
q-p的值,是两个指针指向的元素之间有多少个元素。
q-p 等于3
4、数组元素的引用方法
int a[10];
int *p=a;
a[3] //数组的第3个元素
*(a+3)
*(p+3)
p[3];
5、指向数组元素的指针变量 和 数组名字的区别
int a[10];
int *p=a;
1:数组名字是数组的首地址,是数组第0个元素的地址,是个常量
不能给a赋值。
2:指针变量p,是个变量,就可以给p赋值。
3:给指针变量取地址的时候,&p 这个表达式是 指针的指针。
4:给数组名字取地址的时候,&a 这个表达式是 数组指针。
int main()
{
int a[10];
printf("%p\n",&a);
printf("%p\n",(&a)+1);
}
0xbf8def18
0xbf8def40相差十进制的40
2、指针数组
如果咱们定义一个数组,数组的每个元素都是指针变量,那这个
数组就是一个指针数组。
指针数组的分类:
2:字符指针数组
char * p[10];//定义了一个字符指针数组p,里面有10个字符指针变量,分别是p[0]~p[9]。
short int *p[10];
int *p[10];
long int *p[10];
float *p[10];
double *p[10];
结构体指针数组
函数指针数组
-------------------------
int *p[10];
指针数组名p是数组的首地址,是数组第0个元素的地址,
即p[0]的地址,p[0]是int * 类型的。
所以说p[0]的地址是 int **类型
所以p是int **类型的。
int **q;
q=p;
四:指针的指针
咱们定义指针变量的时候,指针变量在内存中占4个字节,这个指针变量本身也有个地址。
char *p;
指针的指针定义方法:
char* *q;
q=&p;
q也是个指针变量,也有地址,要想存q的地址
char ** *t;
t=&q;
3、数组指针
1:概念:数组指针,本身是个指针,占4个字节,存放一个地址编号。
2:数组指针的定义方法
指向的数组的类型 (*数组指针变量名)[元素的个数];
int(*p)[5];//定义了一个变量p,指向了一个 有5个整型元素的数组
int a[5];
p=&a;
int b[3][5];
p=b;
扩展:指向多维数组的 数组指针
1:定义一个指针,加1 跳一个二维数组
int (*p)[2][3];
int a[4][2][3];
p=a;
int b[2][3];
p=&b;
五:指针和函数之间的关系
1、指针作为函数的参数
咱们可以给一个函数传一个 整型、字符型、浮点型的数据,也可以
给函数传一个地址。
例:
int num;
scanf("%d",&num);
1:传数值
例1:
void swap(int x,int y)
{
int temp;
temp=x;
x=y;
y=temp;
}
int main()
{
int a=10,b=20;
swap(a,b);
printf("a=%d b=%d\n",a,b);//结果为 10 20
}
2:传地址
例2:
void swap(int *p1,int *p2)
{
int temp;
temp= *p1;
*p1=*p2;// p2指向的变量的值,给p1指向的变量赋值
*p2=temp;
}
int main()
{
int a=10,b=20;
swap(&a,&b);
printf("a=%d b=%d\n",a,b);//结果为 20 10
}
例3:
void swap(int *p1,int *p2)//&a &b
{
int *p;
p=p1;
p1=p2;//p1 =&b让p1指向main中的b
p2=p;//让p2 指向 main函数中 a
}//此函数中改变的是p1和p2的指向,并没有给main中的a和b赋值
int main()
{
int a=10,b=20;
swap(&a,&b);
printf("a=%d b=%d\n",a,b);//结果为 10 20
}
总结一句话:要想改变主调函数中变量的值,必须传变量的地址,
而且还得通过*+地址 去赋值。无论这个变量是什么类型的。
例4:
void fun(char *p)
{
p="hello kitty";
}
int main()
{
char *p="hello world";
fun(p);
printf("%s\n",p);//结果为:hello world
}
例5:
void fun(char **q)
{
*q="hello kitty";
}
int main()
{
char *p="hello world";
fun(&p);
printf("%s\n",p);//结果为:hello kitty
}
3:传数组
给函数传数组的时候,没法一下将数组的内容作为整体传进去。
只能传数组的地址。
例1:传一维数组的地址
//void fun(int p[])//形式1
void fun(int *p)//形式2
{
printf("%d\n",p[2]);
printf("%d\n",*(p+3));
}
int main()
{
int a[10]={1,2,3,4,5,6,7,8};
fun(a);
return 0;
}
例2:传二维数组的地址
//void fun( int p[][4] )//形式1
void fun( int (*p)[4] )//形式2
{
}
int main()
{
int a[3][4]={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
fun(a);
return 0;
}
2、指针作为函数的返回值
一个函数可以返回整型数据、字符数据、浮点型的数据,
也可以返回一个指针。
例1:
char * fun()
{
char str[100]="hello world";
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);//打印的内容不可预知。
}
//总结,返回地址的时候,地址指向的内存的内容不能释放
如果返回的指针指向的内容已经被释放了,返回这个地址,也没有意义了。
例2:返回静态局部数组的地址
char * fun()
{
static char str[100]="hello world";
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);//hello world
}
原因是,静态数组的内容,在函数结束后,亦然存在。
例3:返回文字常量区的字符串的地址
char * fun()
{
char *str="hello world";
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);//hello world
}
原因是文字常量区的内容,一直存在。
例4:返回堆内存的地址
char * fun()
{
char *str;
str=(char *)malloc(100);
strcpy(str,"hello world");
return str;
}
int main()
{
char *p;
p=fun();
printf("%s\n",p);//hello world
free(p);
}
原因是堆区的内容一直存在,直到free才释放。
总结:返回的地址,地址指向的内存的内容得存在,才有意义。
3、指针保存函数的地址
咱们定义的函数,在运行程序的时候,会将函数的指令加载到内存
的代码段。所以函数也有起始地址。
c语言规定:函数的名字就是函数的首地址,即函数的入口地址
咱们就可以定义一个指针变量,来存放函数的地址。
这个指针变量就是函数指针变量。
1:函数指针变量的定义方法
返回值类型(*函数指针变量名)(形参列表);
int(*p)(int,int);//定义了一个函数指针变量p,p指向的函数
必须有一个整型的返回值,有两个整型参数。
int max(int x,int y)
{
}
int min(int x,inty)
{
}
可以用这个p存放这类函数的地址。
p=max;
p=min;
2:调用函数的方法
1.通过函数的名字去调函数(最常用的)
int max(int x,int y)
{
}
int main()
{
int num;
num=max(3,5);
}
2.可以通过函数指针变量去调用
int max(int x,int y)
{
}
int main()
{
int num;
int (*p)(int ,int);
p=max;
num=(*p)(3,5);
}
3、函数指针最常用的地方
给函数传参
------------------------------------------------------
<四>bs应用开发
一、常见的web技术
web前端开发技术:HTML\css(做网页)、XML(传输大数据)、AJAX、Javascript(动态的交互)
Web服务器的开发技术:CGI
·数据库管理:SQLite
二、HTML:超文本标记语言
html文档=网页:扩展名.html,最终显示结果取决于web浏览器的显示风格及其对标记的解释能力
css:层叠样式表,做出的效果比.html效果要好
基本规则:选择器 {属性:值;属性:值} h1.a{color:red;font-size:14px}
JavaScript(JS):是一种基于对象并具有安全性能的脚本语言,,由浏览器内解释器翻译成可执行格式后 执行
AJAX:网页与服务器之间的桥梁,前端传向后端,局部刷新
XMLHttpReq:
XML:可扩展标记语言
功能:传输和存储数据
CGI:通过标准输入、标准输出实现与web服务器间信息的传递
步骤:浏览器将用户请求送到服务器;服务器接收到数据请求并交给CGI程序处理;CGI程序把处理结果传送给服务器;服务器把结果传送给浏览器,在浏览器上显示结果
SQLlite:是一个开源的、内嵌式的关系型数据库
安装:sudo apt-get install sqlite3
sudo apt-get install sqliteman
SQL:是一种结构化查询语言,是一种应用最最广的数据库语言
1、创建、打开数据库
sqilte3 *.db
2、创建表:
create table persons(id integer primary key,name text);
3、查看表:.tables/.tab
4、查看数据表的结构:.schema/.sch
5、添加一列信息:alter table persons add sex text;
6、修改表名:alter table 表明 rename to 新表名
7、删除表名:drop table 表名
8、插入新行:insert into 表名 values();insert into persons values(1,'lucy');
部分赋值:insert into 表名(id,name) values(1,'lili');
9、修改表中的数据:update persons set id=2 where name='peter';
10、删除表中的数据:delete from 表名 name='lili';
11、查询select *from persons;
select name from persons where id=1;
.mode column 左对齐列
.headers on 列名显示
12、in 允许我们在where中规定多个值
add ;between A and B; link用于模糊查找 ;not;order by;事务
select *from persons order by id;
select *from persons order by id desc;
select *from persons order by id;
select *from persons order by id;
13、SQLite C 编程
二、SQLite数据库
1、安装字符界面:sudo apt-get install sqlite3
安装图形界面:sudo apt-get install sqliteman
2、 SQL是目前应用最广泛的语言
integer:带符号的整数
real:8字节表示的浮点类型
text:字符类型
blob:任意类型的数据,大小限制
null:表示空
3、 创建、打开数据库: sqlite3 *.db
创建表create: create table persons(id integer,name text);
删除表drop: drop table persons;
修改表alter: *在已存在的表中添加1列 alter table persons add sex text;
**修改表名 alter table persons rename to new_name;
插入新行: *全部赋值 insert into persons values(1,'lili');
**部分赋值 insert into persons(id) values(2);
删除一行或多行:delete from persons where id=‘1’;
修改表中的数据:update persons set id=2,name='bob' where addr='peter';
查询select :select *from persons where id=1;
select name from persons;
select name from persons where id=1;
匹配条件:in select *from persons where id in(1,2);
add、or、between A add B、like select *from persons where addr like '%jing%';
not(取补集)、order by、事务(begin、commit、rollback)
升序:select *from persons order by name;
降序:select *from persons order by name desc;
4、SQLite C编程
sqlite3 *db;
int sqlite3_open("./test.db",&db)//第一个参数是数据库的文件名;第二个参数数据库的标识;
int sqlite3_close(db);
*******针对查询类的语句会用到回调(用到了callback)或者非回调方法(不需要callback)
回调方法callback(void *para,int colnum,char **col_value,char **col_name)//第二个列数,第三个一行中各列值
第四个参数最上面一行的信息
{
}
char *sql="select *from persons";
sqlite3_exec(db,sql,callback,NULL,&errmsg);
非回调方法:sqlite3_get_table(db,sql,&p,&row,&col,&errmsg);//row保存结果集的行数、col保存结果集的列数
sqlite3_free_table(p);
5、SQL语句进阶
**文件处理函数:
length()//返回字符串的长度
lower()//将字符串转换为小写
upper()//将字符串转换为大写
select id,upper(name) from persons;
**聚集函数,用于检索数据,以便分析和报表生成
avg()//某列的平均数
count()//某列的行数
max()//某列的最大值
min()
sum()
select count(*)from persons;
select max(score) from persons;
**数据分组:group by *****
select class,count(*) from persons group by class;
group by 必须放在where 之后
select class,avg(score) from persons where class='class_A' group by class;
**过滤分组:having*****
select class,avg(score) from persons group by class having avg(score)>90;
**约束****
primary key主键值,只有一个;unique唯一约束,可以有多个;check检查约束,保证这一列中的数据满足指定的条件
**联结表(多表操作):就是从多个表中查询数据。****
优点:将学生信息和成绩分开存储,节省空间,处理简单,效率更高,在处理大数据时更为明显
select name,adr,score,year from persons,grade where persons.id=grade.id;
**视图****
创建视图:create view PG as select name,addr,score,year from persons,grade where persons.id=grade.id;
****触发器***
触发器就是数据库的回调函数,它会在指定的数据库时间发生的时候自动执行调用;
只有每当执行delete、insert、updata操作时,才会触发
创建触发器:create trigger record_del after delete on records\
begin delete from stuff where rfid2=old.rfid;end;
当执行delete from records where id=1;时会执行begin 与end 之间的SQL语句
---------------------------------------------------
<五>三种缓冲方法
1:行缓冲
标准c库的输入输出函数,向屏幕(标准输出)写东西的时候是行缓冲的
一:也就是输出的内容会在缓冲区里,直到缓冲里有'\n‘才刷新缓冲区,
二:或者调用fflush(stdout)人为的刷新缓冲区,
三:或者程序结束了刷新缓冲区。
四:缓冲区满了,也会刷新缓冲区。
2:全缓冲
用标准io库函数,向普通文件写东西的时候是全缓冲的,
什么是全缓冲啊? 答:咱们向文件中写的东西,都在缓冲区里,哪怕缓冲区
有'\n'也不会刷新缓冲区,也不会把缓冲区里的东西写到文件里,
那什么时候刷缓冲区啊?
一:缓冲区满了会刷新缓冲区,把缓冲区里的内容刷新到文件里
二:人为的调用fflush(文件指针)可以刷新缓冲区,将缓冲区里的内容刷新到文件里
三:程序结束了,也会将缓冲区里的内容刷新到文件里。
四:关闭文件的时候,也会刷新缓冲区。
3:无缓冲
咱们将来会给大家讲一套系统提供的接口函数,对文件进行读写。
那这些系统调用是不带缓冲的,也就是说,你写东西到文件中的时候,
不经过缓冲区,直接进入文件。
什么是缓冲区:
缓冲区就是一块内存,咱们输入输出,先将数据放到这块内存里,然后再到文件中
或者是数组或变量中。
--------------------------------------------------------
<六>网络编程整理
一、网络协议分为几层:
采用分层机构的目的:为了减少协议设计的复杂度
特点:1)每一层利用下一层提供的服务来为上一层提供服务
2)本层服务对实现细节上对上层屏蔽
1、OSI/RM(理论上的标准)7层
1)应用层; 主要是一些终端的应用,如FTP(各种文件下载),WEB(IE浏览器)(就把他理解为我们在电脑屏幕上
可以看到的东西,就是终端应用)
2)表示层: 主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩(也就是说把计算机能够识别的
东西转换成人能识别的东西)
3)会话层: 通过传输层建立数据传输的通路。主要在你的系统之间发起会话或者接收会话请求。
(设备之间需要互相认识可以是IP 也可以是MAC或者主机名)
4)传输层: 定义了一些传输数据的协议和端口,如TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,
数据量大的数据),UDP(用户数据报协议,用于传输可靠性要求不高,数据量小的数据)主要是将从下层接
收的数据进行分段进行传输,到达目的地址后进行重组(段)
5)网络层: 主要将从下层接收到的数据进行IP地址的封装与解封装。在这一层工作的设备是路由器(数据包)
6)数据链路层:主要将从物理层接收的数据进行MAC地址的封装与解封装。
常把这一层的数据叫做帧。在这一层的设备是交换机,数据是通过交换机来传输的(帧)
7)物理层: 主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质
的传输效率等。 它的主要作用是传输比特流(就是由1、0转化为电流强弱来
进行传输,到达目的地后在转化为1、0,也就是我们说的模数转换和数模转换)
2、TCP/IP分层结构(4层)
1)应用层:向用户提供一组常用的应用程序,比如电子邮件、文件传输访问、远程 登录
2)传输层:定义了一些传输数据的协议和端口,如TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,
数据量大的数据),UDP(用户数据报协议,用于传输可靠性要求不高,数据量小的数据)主要是将从下层接
收的数据进行分段进行传输,到达目的地址后进行重组(段)
3)网络层:主要将从下层接收到的数据进行IP地址的封装与解封装。在这一层工作的设备是路由器(数据包)
4)链路层:对数据进行MAC地址的封包或解包;负责数据帧的发送与接收
二、分组交换(同一分组的长度是一样的,除最后一个之外)
主要采用存储转发技术(重组发生在目的主机)
三、TCP\IP协议
为了使各种不同的计算机之间能够互相通信,ARPANet指定了一套计算机通信协议,即TCP/IP协议。
1、TCP 协议(传输控制协议)
概念:TCP是一种面向连接的,可靠地传输层协议
功能:提供进程间通信的能力
特点:a.建立链接(三次握手)--使用链接--释放链接(四次回手)
b.TCP数据包中包含序号和确认序号
序号:指的是本报文段所发送的数据的第一个字节的序号
确认序号:是期望收到对方下一个报文段的第一个数据字节的序号
c.对包进行排序并检错,而损坏的包可以被重传
d.窗口式流量控制、慢启动和拥塞避免。例子,客户端从服务器发请求看电影
服务对象:需要高度可靠性且面向连接的服务器(Telnet、FTP、SMTP)
tcp中的close函数:
关闭socked创建的监听套接字符将导致服务器无法继续接受新的连接,但不会影响已经建立的连接
关闭accept返回的已连接套接字将导致它所代表的连接被关闭,但不会影响服务器的监听
关闭客户端的连接就是关闭连接,不意味着其他
****tcp的三次握手与四次挥手
三次握手:SYN是TCP包中的syn
2、UDP协议(用户数据报协议)
概念:UDP是一种面向无连接的传输层通信协议
功能:提供进程间通信的能力
特点:a.发数据之前不需要建立连接
b.不对数据包的顺序进行检查
c.没有错误检测和重传机制
服务对象:主要用于那些面向查询--应答的服务 如NFS(网络文件系统) NTP(网络时间协议)
3、为什么TCP不可以发送0长度的数据包而UDP可以?
因为TCP用0长度的数据包标记关闭连接,而UDP根本不需要连接 因此UDP 可以发送0长度数据包
四、MAC/IP
1、MAC地址(物理地址):用于标示网络设备,MAC 尽量不要修改
MAC的作用?
从一个设备搬运到另外一个设备
从一个局域网到另一个局域网源IP与目的IP不变,只是MAC改变
当mac表中没有相应的mac地址时,会调用ARP协议
2、IP地址(逻辑地址)32位
IP使用32bit地址,由子网ID和主机ID 两部分组成
子网ID:IP地址中由子网掩码中1覆盖的连续位()
主机ID:IP地址中由子网掩码中0覆盖的连续位
主机ID全为0的IP表示网段地址;主机ID全为1的IP地址表示该网段的广播地址
公有IP:可以直接连接Internet
私有IP:不可以直接连接Internet,主要用于局域网内的主机联机规划
本地回环地址:
定义:127.0.0.1通常称为本地回环地址
功能:主要是测试本机的网络配置
能ping通127.0.0.1说明本机的网卡和IP协议安装都没有问题
*注意*127.0.0.1~127.255.255.254中的任何地址都将回环到本地主机中,它仅代表设备的本地虚拟接口
打开本地回环地址:ifconfig lo up
关闭:ifconfig lo down
3.ip的作用是什么,不是已经有了mac了,为什么还要IP??
ip是个逻辑地址,MAC物理地址。ip决定数据包起点、终点,而MAC是从起点到终点要经过哪些设备
4.TCP长连接、短连接?
长连接:建立连接--使用链接--释放链接
短连接:(发一个数据建立一次连接、释放一次连接)建立连接--使用链接--释放链接--建立连接--使用链接--释放链接--建立连接--使用链接--释放链接...
5、子网掩码:是一个32Bit由1和0组成的数字,并且1和0连续
作用:指明IP地址中哪些位标识的是主机所在的子网以及哪些位标识的是主机号
*注意*:必须与ip仪器使用,不能单独使用
IP地址中由子网掩码中1覆盖的连续位为子网ID,连0覆盖的为主机ID
ifconfig eth0 172.20.226.5 netmask 255.255.255.0
route add default gw 172.20.265.254
6、IP地址分类:
A类:默认8个bit子网ID,第一位为0
B类:默认16bit子网ID,前两位为10
C类,默认24bit子网ID,前三位为110
D类,前4为1110,多播地址
E类,前5为11110,保留为今后使用
五、端口
TCP/IP协议采用端口标识通信的进程
**为什么需要端口,用pid来标识不就够了吗?
pid是底层给进程分配的进程号。若想将web的端口绑定为8000,使用PID(系统分配,不能人为实现)就不容易实现
六、组包、拆包
七、路由器、交换机、集线器
交换机是一个扩大网络的器材,可以把更多的计算机等网络设备连接到当前的网络
1、集线器:对接收到任何数据都会进行广播发送 物理层
特点:a、能够转发数据
b、每次转发数据都是以“广播”的形式发送,给网络带来一定的拥堵
2、交换机:对接收的数据包按照目的mac指定发送 数据链路层
特点:a、能够转发数据
b、第一次使用“广播”,以后通信都是用“单播”
3、路由器:又称网关设备,就是用来连接不同的局域网 网络层
特点:a、能够转发数据
b、
八、大小端(与系统有关)
1.小端格式:将低位字节数据存储在低地址
2.大端格式:将低位字节数据存储在高地址
3.如何确定自己主机的字节序
定义一个共用体
#include <stdio.h>
int main(int argc,char *argv[])
{
union{
short s;
char ch[sizeof(short)];
}un;
un.s=0x0102;
if(un.ch[0]==1&&un.ch[1]==2)
{
printf("big\n");
}
else if(un.ch[0]==2&&un.ch[1]==1)
{
printf("small\n");
}
else
{
printf("error\n");
}
return 0;
}
十.网络编程TCP/UDP
十一.广播/多播
1.子网定向广播地址 例:192.168.1.255
受限广播地址 :255.255.255.255
ip 、流程、修改套接字选项
2、多播
多播地址:224.0.0.1
多播地址向以太网MAC地址映射
十二、网络的通信过程
设置3个网段,pc机记得设置网关,路由器需要设置路由表
十三.DNS( 域名解析) 例子www.sohu.com对应一个ip
十四、常用的函数
1、字节序转换
htonl, htons, ntohl, ntohs - convert values between host and network byte
order(网络字节序与主机字节序之间的转换)
2、地址转换函数
int inet_pton(int family,const char *strptr,void *addrptr)将点分十进制数转换为32位无符号整数
十五、UDP
1、网络编程接口--socket:提供不同主机上的进程之间的通信
int scoket(int family,int type,0);//第一个参数协议族IPV4 还是IPV6;第二个套接字类型TCP还是UDP;返回值是套接字(文件描述符)
sockfd = socket(AF_INET, SOCK_DGRAM, 0); 产生的套接字默认属性是主动的
2、sendto(int sockfd,const void *buf,strlen(buf),0,(const struct sockaddr *)to,sizeod(to));
功能:向to结构体指针中指定Ip,发送UDP数据//buf是要发送的内容,to是对方的信息(对方的IP、对方的端口号)
3.接收数据稍复杂些
1)绑定bind 发的时候写别人的地址,绑定的时候写自己的端口号;(如:INADDR_ANY)
int bind(int sockfd,(const struct sockaddr *)myaddr,sizeof(myaddr));//
**服务器之所以要bind是因为它的端口需要固定***
2)收数据
recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &sizeof(client_addr));
4.UDP的C/S架构
5.TFTP:简单文件传送协议
下载的过程:
服务器在69号端口等待客户端的请求;服务器若批准此请求,则使用临时端口与客户端进行通信;每个数据包
的编号都有变化,每个数据包都有得到ACK的确认如果超时则需要重新发送最后的数据包;数据长度以512字节传输,若小于512个字节就意味着传输结束
****掌握TFTP通信的过程******由客户端发送一个下载请求(即上传或下载)--服务器的69号端口收到请求后,分配一个临时端口与客户端通信,即发送一个数据包---客户端对收到的数据包进行确认 应答,即回应一个ACK包---数据长度以512字节传输,若小于512个字节就意味着传输结束
上传的过程:
由客户端发送一个上传请求(即上传或下载)--服务器的69号端口收到请求后,分配
一个临时端口与客户端通信,即发送一个应答包ACK---客户端对收到应答包后发送数据包--当发送的数数据小于512时就意味着结束
6广播、多播
广播:destmac ff:ff:ff:ff:ff:ff
多播: 创建一个多播组;将想收到多播的IP加入到多播组;
destmac 01:00:5e:_*:**:**
后面23位填多播组ip的低23位
十六、TCP
1、sockfd = socket(AF_INET, SOCK_STREAM, 0); 产生的套接字默认属性是主动的
2、int connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 主动连接服务器,成功返回0
2、ssize_t send(sockfd,buf,sizeof(buf),0);//成功返回发送的字节数 ****不能用TCP协议发送0长度的数据包
3、ssize_t recv(sockfd,bun,sizeof(buf),0);//成功返回收到的字节数
1、TCP的服务器与客户端的收发
客服端:
socket创建套接字--accept建立连接--send收发数据
服务器:
socket创建套接字--bind绑定信息--listen主动变被动;操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接--
--accept从已连接队列中取出一个已经建立的连接;返回一个已连接套接字--send/recv数据
握手的过程中,会将对方的IP、mac会存放在底层的,进而用send发数据时就不用在写服务器的ip等信息;2-4分钟就是释放该结构体
2、TCP的10种状态
十七、PC机与web服务器通信的全过程
十八、原始套接字
1、数据报套接字(SOCK_DGRAM)--无连接的套接字,针对无连接的UDP服务,只能收发UDP协议的数据
流式套接字(SOCK_STREAM)--面向连接套接字,针对连接的TCP服务,只能收发TCP协议的数据
原始套接字(SOCK_RAW)--可以收发内核没有处理的数据包,因此访问其他协议的数据需要用原始套接字
--------------------------------------------------
<七>系统编程
操作系统是用来管理所有资源,将不同的设备和不同的程序关联起来
1、return 与exit的区别?
return 是退出当前的函数,返回到被调用的地方
exit 是退出当前所在的进程
2、strlen 与sizeof的区别?
首先,strlen是个库函数 ;sizeof是个关键字
char buf[]="abcd";
strlen(buf)=4;sizeof(buf)=5;
read 用sizeof;write用strlen
3、const作用:定义一个不可修改的常量const int a=10;下面就不能对a 进行复制
4、系统调用I/O函数
open close write read
int fd=open("a.txt",O_CREAT|O_RDWR,0777);返回文件描述符,失败返回-1
close(fd);成功返回0,失败返回-1
write(fd,buf,strlen(buf)); 返回实际写入的个数,失败返回-1
read(fd,buf,sizeof(buf));返回实际读取的个数,失败返回-1
程序:是静态的,是存放在存储介质上的一种可执行文件
进程:正在运行的可执行程序,是动态的 三种状态(就绪态---执行态---等待态--就绪态--)
运行的程序都是通过exec函数由内核将程序读入内存,使程序执行起来 就成为一个进程
5、系统调用:操作系统提供给用户的一组特殊的函数接口,即可以直接操作内核
库函数:分为2类:1、不需要调用系统调用(即不需要走内核):strcpy,bzero
2、需要调用系统调用(即需要切换到内核空间,需要通过封装系统调用去实现相应的功能):printf\fread
并不是所有的系统调用都封装成库函数; 系统提供的很多功能都需要通过系统调用才能实现
系统调用时需要时间的,降低效率。所以库函数访问文件的时候根据需要,设置不同的缓冲区,从而减少了直接调用
系统调用的次数,提高了发麻花纹的效率
单次调用时系统调用较快;多次调用时,库函数较快(因为库函数设置了缓冲区)
6、printf 的内容输出有4中情况:1、加'\n' 2、程序结束 3、缓存区已满 4、手动刷新fflush(stdout)
7、for(i=0;i<2;i++)
{
fork();
printf("A");
}问A打印几次?
8、操作系统的5大功能(系统调用的接口)?
进程管理、内存管理、文件系统、设备控制、网络
9、进程调度的方式:时间片轮转、优先级高的先执行、
一、进程
1、进程号的范围:signed short int: 0~0x7fff(32767)非负整型
2、进程:
1、创建进程fork、vfork
pid_t pid=fork();//子进程中返回0,父进程中返回子进程的进程号
其创建的子进程是父进程的一个复制品,子进程所独有的只有它的进程号、计时器
子进程从fork后执行,父子进程谁先执行不一定;
进程:执行多任务;父子进程各自有自己的堆、栈、静态全局区、文字常量区、代码区;
子进程复制父进程的缓冲区;父进程的文件描述符也被复制到子进程中去
pid_t pid=vfork();
区别:1、vfork保证子进程先运行;在调用exec或exit之后父进程才执行
2、在子进程中调用exec或exit之前,它在父进程的地址空间中运行,在exec之后会有自己的进程空间
exec函数族,在进程中启动另一个程序
system(buf);//buf 内相当于在终端下写的命令,如system("ls -alh");
system 是封装的一个库函数,system会调用fork产生子进程,子进程调用exec启动、bin/sh -c cmd,
命令执行完后返回原调用进程
2、进程的挂起:sleep中进程处于等待态
sleep(3);//返回值:若进程挂起到指定的秒数返回0,否则返回剩余的秒数
3、等待函数wait/waitpid
int status=0;
pid_t wait (&status);
功能:1、等待子进程结束,父进程回收子进程的资源
2、调用wait的进程会挂起即阻塞
3、若调用wait的进程没有子进程或它的子进程已经结束,该函数立即返回
返回值:成功返回子进程的进程号;失败返回-1
waitpid(pid,&status,0|WNOHANG|WUNTRACED)//可以指定哪个子进程
4、exit\_exit();结束正在运行的进程
exit(value);
区别:exit是库函数,结束进程前 关闭文件描述符、刷新缓冲区
_exit是系统调用
atexit函数注册退出处理函数;注册是栈的原理
3、三种特殊的进程:
1、孤儿进程:父进程结束,子进程没有结束;孤儿进程的父进程号是1。想结束子进程用kill 进程号
2、僵尸进程:子进程结束,父进程没有结束,没有回收子进程的资源(即父进程没有调用wait或者waitpid)
3、守护进程(精灵进程):特殊的孤儿进程,这种进程脱离进程在后台运行
4、continue\break\return、exit\_exit的区别?
continue:结束本次循环,进入下次循环
break:1、跳出循环 2、用在switch case中
return:1、结束return所在的函数;2、返回函数被调用的地方
exit\_exit();结束所在的进程
5、 三种缓冲?
1:行缓冲
标准c库的输入输出函数,向屏幕(标准输出)写东西的时候是行缓冲的
一:也就是输出的内容会在缓冲区里,直到缓冲里有'\n‘才刷新缓冲区,
二:或者调用fflush(stdout)人为的刷新缓冲区,
三:或者程序结束了刷新缓冲区。
四:缓冲区满了,也会刷新缓冲区。
2:全缓冲
用标准io库函数,向普通文件写东西的时候是全缓冲的,
什么是全缓冲啊? 答:咱们向文件中写的东西,都在缓冲区里,哪怕缓冲区
有'\n'也不会刷新缓冲区,也不会把缓冲区里的东西写到文件里,
那什么时候刷缓冲区啊?
一:缓冲区满了会刷新缓冲区,把缓冲区里的内容刷新到文件里
二:人为的调用fflush(文件指针)可以刷新缓冲区,将缓冲区里的内容刷新到文件里
三:程序结束了,也会将缓冲区里的内容刷新到文件里。
四:关闭文件的时候,也会刷新缓冲区。
3:无缓冲
咱们将来会给大家讲一套系统提供的接口函数,对文件进行读写。
那这些系统调用是不带缓冲的,也就是说,你写东西到文件中的时候,
不经过缓冲区,直接进入文件。 write是系统调用,无缓冲
什么是缓冲区:
缓冲区就是一块内存,咱们输入输出,先将数据放到这块内存里,然后再到文件中
或者是数组或变量中。
6、进程间的通信功能
1、数据传输:一个进程需要将它的数据发送给另一个进程(无名管道、有名管道)
2、资源共享:多个进程之间共享同样的资源(消息队列、共享内存)
3、通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件(信号)
4、进程控制:
1、无名管道、有名管道
管道(pipe):在应用层体现为两个文件描述符fd[0]\fd[1];管道在内核中
特点:半双工;数据只能从管道一端写入,另一端读出;先入先出;管道存在于内存中;
管道中的数据一旦被读走,就从管道中被删除;
缺点:管道没名字,只能在父子孙进程间使用
***要在创建子进程之前,创建管道;在创建子进程时,管道不会复制,父子进程指向同一个管道;
但fd[0]\fd[1]会复制********
1、创建一个无名管道pipe
int fd[2];
int ret=pipe(fd);//成功返回0,失败返回-1;fd[1]写入,fd[0]读出
***先创建管道,后创建子进程 ;read默认情况下是阻塞的******
2、文件描述符的复制 dup\dup2
int dup(int oldfd);
返回值:成功返回新文件描述符;失败返回-1
int dup2(int oldfd,int newfd);
返回值:成功返回newfd,失败返回-1
特点:通过dup\dup2复制的文件描述符,newfd、oldfd指向同一个文件
命名管道(fifo)
特点:当使用命名管道的进程退出后,该管道还存在;
fifo有名字,不想关的进程之间也可以通信
1、创建一个有名管道mkfifo
int mkfifo("./fifo",0666);//成功返回0,失败返回-1 ;./fifo相当于一个文件,对齐open、write、read
2、消息队列、共享内存
消息队列: 是消息的链表,存放在内存中,由内核维护
特点:1、从消息队列中读出消息后,相应的数据被删除;2、消息队列允许多个进程向它写入或读取消息
3、消息可以随机查询;4、消息是有类型的 5、只有内核重启或人工删除,消息队列才能被删除
******通过KEY值就可以在系统内获得一个唯一的消息队列标识符
1、产生一个key值
key_t key=ftok(const char *pathname,int num);
功能:产生一个key 值;
返回值:成功返回key值,失败返回-1
2、创建一个消息队列
int msgid=msgget(key_t key,int msgflg);
功能:创建一个新的或打开一个已经存在的消息队列,只要key值相同就能得到同一个消息队列的标识符
返回值:成功返回消息队列的标识符,失败返回-1
3、发送消息
int msgsnd(int msgid,&send_msg,sizeof(send_msg)-4,0);
4、接收消息
ssize_t msgrcv(int msgid,&recve_msg,sizeof(send_msg)-4,类型,0);
5、消息队列的控制
int msgctl(int msgid,int cmd,struct msqid_ds *buf);//第三个参数可以传NULL
参数:cmd:IPC_RMID(删除消息队列)
IPC_STAT(将msqid相关的数据结构中的各个元素的当前值存入到由buf指向的结构中)
IPC_SET(将msqid相关的数据结构中的元素设置为由buf指向的结构中对应的值)
typedef struct _msg
{
long mtype;//消息的类型
char mtext[1024];//消息的内容
}MSG;
key_t key;
int msg;
MSG snd_msg,rcv_msg;//MSG是个结构体
key = ftok("MSG_PATH", MSG_PROID);//产生一个key 值,全局变量
msg = msgget(key, IPC_CREAT | 0777);//创建消息队列
bzero(&snd_msg, sizeof(MSG));
snd_msg.mtype = MSG_CGI_TO_SERVER_TYPE;//MSG_CGI_TO_SERVER_TYPE=100的类型
strcpy(snd_msg.mtext, buff_decode);
msgsnd(msg, &snd_msg, sizeof(MSG)-4, 0);//发送消息
msgrcv(msg, &rcv_msg, sizeof(MSG)-4, MSG_SERVER_TO_CGI_TYPE, 0);//接收消息MSG_SERVER_TO_CGI_TYPE=200的类型
共享内存:允许两个或者多个进程共享给定的存储区域
特点:1、共享内存是进程间共享数据的一种最快的方法(一个进程向共享的内存区域写入数据,共享这个内存的所有进程可以立刻看到)
2、使用共享内存要注意的是多个进程之间对一个给定存储区访问的互斥
****每个进程都会有一个虚拟内存,用的时候会映射到物理内存上去;在虚拟内存中写东西就相当于对共享内存中写东西;共享内存在物理内存中
1、产生一个key值
key_t key=ftok(const char *pathname,int num);
2、创建或打开共享内存
int shm_fd=shmget(key,size_t size,int shmflg);
功能:创建或打开一个共享内存
返回值:成功返回共享内存的标识符,失败返回-1
3、共享内存映射
void *shmat(shm_fd,NULL,0);
功能:将一个共享内存段映射到调用进程的数据段中
返回值:成功返回共享内存段映射的地址
4、解除共享内存映射
int shmdt(const void *shmaddr);
参数:共享内存映射地址
功能:将共享内存与当前进程分离(仅仅是断开联系,并没有删除共享内存)
5、共享内存的控制
int shmctl(int shmid,IPC_RMID,NULL);
功能:删除共享内存
例:key_t key;
int shm_fd;
key=ftok("./",2015);
shm_fd=shmget(key,1024,IPC_CREAT|0666);
3、通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件(信号)
信号是异步通信,是软件中断
1、kill(pid_t pid,int signum);
功能:给指定进程发送信号。
返回值:成功返回0,失败返回-1;
2、unsigned int alarm(unsigned int seconds);
功能:在seconds秒后,向调用进程发送一个SIGALRM信号,使进程终止
返回值:若以前没有设置过定时器,或定时器已超时,返回0;
否则返回定时器剩余的秒数,并重新设置定时器
例:alarm(3);
while(1)
{
printf("hello\n");
}
结果:hello
hello
hello
闹钟
3、int raise(int signum);//自杀
功能:给调用进程本身送一个信号
4、void abort(void);//进程会退出
5、int pause(void);//挂起进程,直至捕捉到信号为止
6、signal(int signum,void handler(int))//注册信号处理函数 ctrl+c是2号信号
忽略信号signal(2,SIG_IGN);//程序执行时,按ctr+c就不能在中断进程
4、进程控制:
二、线程
1、创建一个线程
pthread_t thread;//线程标识符
`` int pthread_create(&thread,NULL,void *(*fun)(void *),void *arg);
2、int pthread_join(thread,void **retval);//第二参数用来存储线程退出的状态
(调用此函数会使函数阻塞)
3、线程分离函数
int pthread_detach(thread);
功能:使调用线程与当前进程分离,使其成为一个独立的线程,该线程终止时,系统将自动回收它的资源(不阻塞);但是线程还是依赖进程
4、退出线程(自杀)
void pthread_exit(void *retval);
参数:存储线程退出状态的指针
5、取消线程(它杀 )
int pthread_cancel(thread);
三、什么是线程、进程?它们的区别?它们的联系?
网络语言:
每个进程都有自己的数据段、代码段、堆栈段,这就造成在进程创建、切换、撤销操作的时候,需要较大的系统开销。
为了减少系统开销,从进程演化出了线程
线程存在于进程,共享进程的资源
1、什么是进程?
进程是具有一定独立功能的程序,它是系统中程序执行和资源分配的基本单位,也就是说进程是一个可以独立运行的一端程序
2、什么是线程?
线程是CPU调度和分派的基本的单位,线程自己基本上不拥有系统资源,在运行时只是暂用一些计数器、寄存器和栈
3、它们之间的关系?
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源
3、线程在执行过程中需要协作同步。不同进程的线程间要利用消息通信的办法实现同步
4、处理机分给线程,即真正在处理机上工作的是线程
5、线程是指进程内的一个执行单元,也是进程内的可调度实体
4、它们间的区别
调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
并发性:不仅进程间可以并发执行,同一个进程的多个线程间也可以并发执行
拥有资源:进程是拥有资源的一个独立单位,此线程不拥有系统资源,但可以访问隶属于进程的资源。
自己的语言:进程之间不能共享资源,即使使用共享内存或者用消息队列,但需要创建相关的标识符,使用的时候
也是相当麻烦,而且 创建多个进程需要的系统开销也比较大;而线程可以共享进程的资源,比较方便 而且所需的
系统开销也比较小。。。但是创建进程也有优点,像调用exec函数族,就必须用进程,因为调用exec 他会将进程中所有的代码替换掉, 那么用线程的话 在同一进程中的线程就会全部崩溃掉
线程是调度和分配的基本单位,进程作为拥有资源的基本单位
系统开销:在进程切换时候,要保存当前进程CPU环境,还要
5、什么时候用多线程?
1、多任务程序的设计:一个线程干一个事 2、并发程序设计:多个线程干一个事 3、数据共享
四、多任务的互斥和同步
互斥:一个公共资源同一个时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源
同步:两个或两个以上的进程或线程在运行过程中按预定的先后次序运行
互斥锁(mutex):用来对共享资源的访问,只能用来处理互斥
只存在两种状态:上锁 和 解锁
1、互斥锁的初始化:
静态分配的互斥锁(在一个进程中只能有一把锁,用完不需要销毁锁)
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
动态分配的互斥锁(在一个进程中可以有多把锁,用完需要销毁锁)
pthread_mutex_t mutex;//mutex是互斥锁
pthread_mutex_init(&mutex,NULL);
2、上锁
pthread_mutex_lock(&mutex);
功能:对互斥锁上锁,若已经上锁,则调用者一直阻塞 直到互斥锁解锁
3、解锁
pthread_mutex_unlock(&mutex);
4、销毁
pthread_mutex_destroy(&mutex);
信号量:本质上是一个非负的计数器;用于进程和线程间的同步和互斥;
当信号量>0时,可以访问:否则阻塞
p操作使信号量sem减1,V操作使信号量sem加1
同步时,设置多个信号量sem;互斥时,只设置一个信号量sem
1、创建一个信号量
sem_t *sem;
int sem_init(&sem,int pshared,unsigned int value);
sem_init(&sem,0,1);
参数:sem:信号量
pshared:等于0,信号量在线程间共享;不等于0,信号量在进程间共享
value:信号量的初始值
2、sem减1
int sem_wait (&sem);
功能:将信号量的值减1,若信号量的值小于0,此函数会引起阻塞
3、sem加1
int sem_post(&sem);
功能:将信号量加1;并发出信号唤醒等待线程
4、删除sem 标识的信号量
int sem_destroy(sem_t *sem);
信号量分为两种:有名信号量(一般用于进程间的同步与互斥)、无名信号量(一般用于线程间的同步、互斥)
进程中的信号量:
void print(sem_t *print1, sem_t *print2)
{
int i = 0;
while(1)
{
sem_wait(print2);
i++;
printf("in print2 i = %d\n", i);
sleep(1);
sem_post(print1);
}
}
int main(int argc, char **argv)
{
sem_t *print1, *print2;
//利用sem_open可以返回一个信号量的地址
******** print1 = sem_open("sem_print1", O_CREAT, 0777, 0);
if(SEM_FAILED == print1)
{
perror("sem_open");
}
print2 = sem_open("sem_print2", O_CREAT, 0777, 1);
if(SEM_FAILED == print2)
{
perror("sem_open");
}
print(print1, print2);
return 0;
}
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
sem_t sem_p,sem_c;
int num=2;
pthread_mutex_t mutex;
void *custom_fun(void *arg)
{
while(1)
{
sem_wait(&sem_p);//进行P操作
pthread_mutex_lock(&mutex);
num++;
printf("producte totle num===%d\n",num);
pthread_mutex_unlock(&mutex);
sem_post(&sem_c);//进行V操作
sleep(1);
}
}
void *product_fun(void * arg)
{
while(1)
{
sem_wait(&sem_c);
pthread_mutex_lock(&mutex);
num--;
printf("coustom totle num===%d\n",num);
pthread_mutex_unlock(&mutex);
sem_post(&sem_p);
sleep(2);
}
}
main(int argc, char *argv[])
{
pthread_t pth_c,pth_p;
pthread_mutex_init(&mutex,NULL);//互斥锁的初始化
sem_init(&sem_p,0,8);//创建两个信号量,并初始化
sem_init(&sem_c,0,2);
pthread_create(&pth_c,NULL,custom_fun,NULL);//创建两个线程
pthread_create(&pth_p,NULL,product_fun,NULL);
pthread_join(pth_c,NULL);
pthread_join(pth_p,NULL);
return 0;
}