Linux网络开发基础
摘要:本人嵌入式菜鸡程序猿一枚,整理自己linux设备驱动的学习笔记。以备自己查漏补缺,有错误之处希望各位大佬不吝赐教。
内存管理和使用
堆和栈
一个计算机应用程序在内存中可分为两个部分:存放代码的代码段和存放数据的数据段。数据段可以分为堆和栈以及全局初始化数据区和未初始化数据区。
全局初始化数据区
未初始化数据区(BSS区)
栈
是一个由编译器分配释放的区域,用来存放函数的参数、局部变量。函数的调用和栈的使用方式保证了不同函数内部定义不会混淆。
堆
一般位于bss段和栈之间,用来动态分配内存。这段内存又程序员来管理。
//test.c
int g_var_a = 0;//全局初始化区
char g_var_b;//未初始化化数据区
int main()
{
int var_a;//栈
char var_str[] = "string1";//栈
char *p_str1,*p_str2;//栈
char *p_str3 = "string2";//存放在已初始化区,p_str4存放在栈
static int var_b = 100;//全局初始化区
p_str1 = (char*)malloc(1024);//堆
p_str2 = (char*)malloc(2048);//堆
free(p_str1);
free(p_str2);
return 0;
}
内存管理函数
malloc()和free()
#include stdlib.h
int *p_mem = (int*)malloc(1024);
free(p_mem);
calloc()和realloc()
calloc()分配一块新内存,realloc()用来改变一块已经分配的内存大小。
calloc()定义如下
void *calloc(size_t nmemb,size_t size);
malloc和calloc的区别,malloc分配内存空间后不能初始化内存,calloc在分配空间后会初始化新分配的空间。
#include stdlib.h
int *p_mem = (int*)calloc(1024,sizeof(int));
#include stdlib.h
int *p_mem = (int*)malloc(1024*sizeof(int));
memset(p_mem,0,1024*sizeof(int));
realloc()函数 可以调整ptr指定的内存空间的大小
void *realloc(void *ptr,size_t size);
int *p_mem = (int*)malloc(1024);
p_mem = (int*)realloc(2048);
内存管理案例
//代码:c_memory_test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *p_str1,*p_str2;
p_str1 = (char*)malloc(32);
if (NULL == p_str1) {
printf("Alloc p_str1 memory ERROR!\n");
return -1;
}
p_str2 = (char*)calloc(32,sizeof(char));
if (NULL == p_str2){
printf("Alloc p_str2 memory ERROR!\n");
free(p_str1);//注意:这里需要释放p_str1占用的内存
return -1;
}
strcpy(p_str1,"this is a simple sentence.");
strcpy(p_str2,p_str1);
print("p_str1 by malloc(): \n");
printf("p_str1 address:0x%.8x\n",p_str1);
printf("p_str1: %s(%d chars)\n",p_str1,strlen(p_str1));
print("p_str2 by calloc(): \n");
printf("p_str2 address:0x%.8x\n",p_str2);
printf("p_str2: %s(%d chars)\n",p_str2,strlen(p_str2));
p_str1 = (char*)realloc(p_str1,16);
if (NULL == p_str1){
printf("Realloc p_str1 memory ERROR!\n");
free(p_str2);//注意,需要释放p_str2占用的内存;
return -1;
}
p_str1[15] = '\0';//写字符串结束符
p_str2 = (char*) realloc(p_str2,128);
if (NULL == p_str2){
printf("Relloc p_str2 memory ERROR!\n");
free(p_str1);
return -1;
}
strcat(p_str2,"The second sentence in extra memory after realloced");
print("p_str1 by realloced\n");
printf("p_str1 address:0x%.8x\n",p_str1);
printf("p_str1: %s(%d chars)\n",p_str1,strlen(p_str1));
print("p_str2 by realloced\n");
printf("p_str2 address:0x%.8x\n",p_str2);
printf("p_str2: %s(%d chars)\n",p_str2,strlen(p_str2));
/*注意,最后要释放占用的内存*/
free(p_str1);
free(p_str2);
printf("OK OK!\n");
return 0;
}
gcc -c c_memory_test.c -Wformat=0 -o memory_test
ANSI C文件管理
定义:文件是可以永久存储的、有特定顺序的一个有序、有名称的字节组成的集合。在Linux系统中,通常能见到的目录、设备文件和管道等,都属于文件,但是具有不同的特性。
//文件指针FILE
typedef struct {
int level;//缓冲区填充的级别
unsigned flags;//文件状态标志
char fd;//文件描述符
unsigned char hold;
int bsize;//缓冲区大小
unsigned char _FAR *buffer;//数据传输缓冲区
unsigned char _FAR *curp;//当前有效指针
unsigned istemp;
short token; //供有效性检查使用
}
linux操作系统屏蔽了操作文件的IO和物理细节,操作文件只需要操作流。
标准输入、标准输出和标准错误
stdin 标准输入,默认读取键盘数据
stdout 标准输出,默认向屏幕输出数据
stderr 默认向屏幕输出数据
缓冲
缓冲机制是为了减少外部设备的读写次数。使用缓冲也能提高应用程序的读写性能。
- 全缓冲
- 行缓冲
- 不带缓冲
//两个设置缓冲的函数接口
void setbuf(FILE *fp,char *buf);
int setvbuf(FILE *fp,char *buf,int mode,size_t size);
#include <stdio.h>
#define BUFSIZE
char *buf = (char*)malloc(BUFSIZE);
setbuf(stdout,buf);
printf("Set STDOUT full buffer OK!\n");
setbuf(stdout,NULL);
printf("set STDOUT no buffer OK!\n");
setvbuf()依靠mode参数实现I/O流设置制定类型的缓冲
_IOFBF:全缓冲
_IOLBF:行缓冲
_IONBF:不带缓冲
#include <stdio.h>
#define USER_BUFF_SIZE 1024
int main(void){
setvbuf(stdout,buf,_IOFBF,USER_BUFF_SIZE);
printf("set STDOUT to user custom buffer size OK!\n");
setvbuf(stdout,NULL,_IOLBF,USER_BUFF_SIZE);//让库来决定stdout列缓冲的大小
printf("make lib set buf OK!\n")
setvbuf(stdout,NULL,_IONBF,NULL);
printf("release buf OK!\n");
return 0;
}
打开关闭文件
mode参数 | 说明 |
---|---|
r/rb | 读打开 |
w/wb | 写打开,并把文件结尾置为0 |
a/ab | 文件结尾添加打开 |
r+/r+b/rb+ | 读和写打开 |
w+/w+b/wb+ | 写打开,并把长度置为0 |
a+或a+b或ab+ | 在文件结尾读写打开 |
FILE *fp = fopen(const char *path,const char *mode);
int fclose(FILE *stream);
读写文件
//每次都读一个
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
//每次写一个
int putc(int c,FILE *stream);
int fputc(int c,FILE *stream);
int putchar(int c);
//每次读一行
char *fgets(char *s,int size,FILE *stream);//size指定读入缓冲s的字符个数
char *gets(char *s);
//每次写一行
int fputs(const char *s,FILE *stream);
int puts(const char *s);
size_t fread(void *ptr,size_t size,size_t,nmemb,FILE*stream);
size_t fwrite(const void *ptr,size_t size,size_t nmemb,FILE *stream);
#include <stdio.h>
int main()
{
int buf[1024] = {0};
int p;
FILE *fp = fopen("./blk_file.dat","rb+");
if(NULL == fp)
return -1;
fwrite(buf,sizeof(int),1024,fp);//写入1024个数据块到文件流fp,每个数据块占4B
/*修改buf的数据,供读取后比较*/
for(i=0;i<16;i++)
buf[i] = -1;
p = &buf[0];//指针p指向buf,供文件读取数据
fread(p,sizeof(int),1024,fp);//读取1024个数据块到buf
/*打印从文件读取的二进制数据*/
for(i=0;i<1024;i++)
printf("buf[%d] = %d\n",i,buf[i]);
fclose(p);
return 0;
}
重新定位
int fseek(FILE *stream,long offset,int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
#include <stdio.h>
int main()
{
FILE *fp = NULL;
char *buf[3] = {
"this is first line!\n",
"Second line!\n",
"OK,the last line!\n"
};
char tmp_buf[3][64],*p;
int i;
fp = fopen("chap7_demo.dat","wb+");
if(NULL == fp){
printf("open file chap7_demo.dat failed!\n");
return -1;
}
for(i=0;i<3;i++)
{
fputs(buf[i],fp);
printf("ftell(fp) = %d\n",ftell(fp));
}
fseek(fp,0,SEEK_SET);//把文件指针设置到文件开头,相当于rewind(fp)
for(i=0;i<3;i++){
p = tmp_buf[i];
fgets(p,64,fp);
printf("%s",p);
printf("ftell(fp) = %d\n",ftell(fp));
}
fclose(fp);
return 0;
}
VFS
虚拟文件系统是具体文件系统和Kernel之间的桥梁。它将各个文件系统抽象出来并提供一个统一的机制来管理和组织各个文件系统。
inode dentry
POSIX文件I/O编程
POSIX文件操作的函数基本上和计算机设备驱动的底层操作是一一对应的。可以把POSIX文件操作理解为堆设备驱动操作的封装。
- POSIX 文件描述符是int类型的一个整数值。
- 任何打开的文件都将被分配一个唯一标识该打开的文件描述符
- LInux系统默认一个进程最多打开1024个文件,使用ulimit -n命令查询系统允许进程打开文件的数量
zydz@zydz-virtual-machine:/mnt/hgfs/sd/file_test$ ulimit -n
1024
默认打开的文件流有stdin/stdout/stderr,文件描述符分为是0,1,2;以后打开的文件描述符分配依次增加。使用**fileno()**可以返回一个流对应的文件描述符;
open()/create()
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname,int flags,mode_t mode);
//flags:O_RDONLY 0 O_WRONLY 1 O_RDWR 2
//mode S_IRWXU S_I(R/W/X)(U/G/O)
int open(pathname,flags,mode);
close()
#include <unistd.h>
int close(int fd);
read()
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t count);//出错返回-1
write()
ssize_t read(int fd,void *buf,size_t count);//出错返回-1
lseek()
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fildes,off_t offset,int whence);//whence:SEEK_SET/ SEEK_CUR /SEEK_END
fstat()
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path,struct stat *buf);
int fstat(int filedes,struct stat *buf);
int lstat(const char *path,struct stat *buf);
文件空间映射mmap()函数
#include <sys/mman.h>
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void *start,size_t length);
文件属性 fcntl()
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd,int cmd);
int fcntl(int fd,int cmd,long arg);
int fcntl(int fd,int cmd,struct flock *lock);//返回值依赖cmd,如果出错返回-1;
//cmd F_DUPFD F_GETFD F_SETFD F_SETFL F_GETFL F_GETOWN F_SETLK
文件输入输出控制ioctl()
#include <sys/ioctl.h>
int ioctl(int d,int request,...);
posix文件编程实例
//posix文件编程实例
#include <sys/types.h> //包含系统基本数据类型
#include <sys/stat.h> //包含文件状态
#include <unistd.h>
#include <fcntl.h>//包含文件控制定义
#include <string.h>
#include <stdio.h>
#include <errno.h>
extern int errno;
int main()
{
int fd,file_mode;
char buf[64] = "this is a posix file!(line1)\n";
off_t curr_pos;
fd = open("./posix.data",O_CREAT|O_RDWR|O_EXCL,S_IRWXU);//打开一个不存在的文件,并创建文件,权限是用户可读写执行
if (-1 == fd) {
switch(errno) {
case EEXIST:
printf("File exist!\n");
break;
default:
printf("open file fail!\n");
break;
}
return 0;
}
write(fd,buf,strlen(buf));
curr_pos = lseek(fd,0,SEEK_CUR);//取得当前文件的偏移位置
printf("FILE POINT at: %d\n",(int)curr_pos);
lseek(fd,0,SEEK_SET);//把文件偏移到文件开头
strcpy(buf,"File Pointer Moved!\n");
write(fd,buf,strlen(buf));
file_mode = fcntl(fd,F_GETFL);//获取文件状态标记
if(-1 != file_mode) {
switch(file_mode & O_ACCMODE) { //检查文件状态
case O_RDONLY:
printf("file mode is READ ONLY!\n");
break;
case O_WRONLY:
printf("file mode is WRITE ONLY!\n");
break;
case O_RDWR:
printf("file mode is READ & WRITE!\n");
break;
}
}
close(fd);
return 0;
}
开发多线程多进程
多进程开发
什么是进程
一个进程不仅包含了正在运行的代码也包括了运行代码所需要的资源。操作系统通过一个称做PCB的数据结构管理一个进程。在一个CPU上,同一时间内,只能有一个进程在工作;
进程属性和环境
用户的程序在运行时,操作系统会在调用main()之前调用exec()函数。exec()系统会先调用一个特殊的启动例程,负责从操作系统读取程序的命令行参数。
int main(int argc,char *argv[],char *envp[]);
/*argc表示argv有多少个参数,envp以name = value的形式存放了一组进程运行中可能用到的环境变量。应用程序可以通过getenv()和putenv()函数读取或设定一个环境变量。
#include <stdlib.h>
char *getenv(const char *name);//name是执行的环境变量
POSIX.1标准定义的环境变量
变量 | 含义 | 变量 | 含义 |
---|---|---|---|
HOME | 起始目录 | LC_TIME | 本地时间/时间格式 |
LANG | 本地名(本地语言类型) | LONNAME | 登录名 |
LC_ALL | 本地名 | NLSPATH | 消息类模板系列 |
LC_COLLATE | 本地排序名 | PATH | 搜索可执行文件的路径 |
LC_CTYPE | 本地字符分类名 | TERM | 终端类型 |
LC_MONETARY | 本地货币类型 | TZ | 时区信息 |
LC_NUMERIC | 本地数字编辑名 |
//getenv.c
#include <stdio.h>
#include <stdlib.h>
int main(){
char *env_path = "PATH";//打算获取的环境变量名称
char *env_name = NULL;
env_name = getenv(env_path); //使用系统函数获取指定环境变量
if (NULL == env_name) {
printf("Not Found!\n");
}
printf("Get Env PATH:\n%s",env_name);
return 0;
}
进程除了环境变量外,还具备本身的基本属性
- PID (Process ID)进程号
- PPID (Parent Process ID)父进程号
- PGID (Process Group ID)进程组号
- UID (User ID) 用户的唯一标识号
- GID(Group ID)用户组的唯一标识号
- EUID(Effective User ID) 以其他用户身份访问文件使用
- EGID(Effective Group ID)以其他用户组身份访问文件使用。
创建进程
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid;//定义个pid_t类型的全局变量用来存放进程号
pid = fork();//创建进程
/*从这里开始,程序已经不是一个进程了,而是被子进程和父进程执行。全局变量pid也被复制一份到子进程中。所以15行和11行的pid值是不样的。*/
if (-1 == pid) {
printf("Error to create new process!\n");
return 0;
}
else if (pid == 0){//子进程
printf("Child Process!\n");
}else { //父进程
printf("Parent process! Child Process ID:%d\n",pid);
}
return 0;
}
等待进程结束 waitpid()
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h> //waitpid()需要的头文件
#include <stdlib.h>
int main() {
pid_t pid,pid_wait;
int status;
pid = fork();//创建子进程
if (-1 == pid) {
printf("Error to create new process!\n");
return 0;
}
else if(pid == 0){//在子进程
printf("Child Process!\n");
}
else{//在父进程
printf("Parent Process!Child Process ID:%d\n",pid);
pid_wait = waitpid(pid,&status,0);
printf("Child process %d returned!\n",pid_wait);
}
return 0;
}
退出进程exit(); _exit() ;atexit(); on_exit()
//bye_example.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void bye(void)
{
printf("That was all,folks\n");
}
void bye1(void)
{
printf("This should called first!\n");
}
int main()
{
int i;
i = atexit(bye);//设置退出回调函数并检查返回结果
if(i != 0) {
fprintf(stderr,"cannot set exit funciton bye\n");
return EXIT_FAILURE;
}
i = atexit(bye1);//按照atexit()函数注册的特点,最后注册的函数会先执行。bye1()先执行
if (i != 0) {
fprintf(stderr,"cannot set exit funciton bye1\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
zydz@zydz-virtual-machine:/mnt/hgfs/sd/file_test$ gcc bye_example.c -o bye_example
zydz@zydz-virtual-machine:/mnt/hgfs/sd/file_test$ ./bye_example
This should called first!
That was all,folks
常用进程间通信的方法
1.管道(简单、效率低、半双工)
管道的本质是内核中的一个缓存,当进程创建一个管道后,Linux会返回两个文件描述符,一个是写入端的描述符,一个是输出端的描述符。可以通过这两个描述符往管道里面写入或者读取数据。
一条管道只能一个进程写,一个进程读,而且不能同时进行。不适合进程间频繁的交换数据。
//使用管道的方法如下:
#include <unistd.h>
int pipe(int filedes[2]);//filedes返回两个文件描述符。filedes[0]为读端,filedes[1]为写端
//pipe_example.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd[2];
pid_t pid;
char buf[64] = "I'm parent process!\n";
char line[64];
if (0 != pipe(fd)) {//创建管道并检查结果
fprintf(stderr,"Fail to create pipe!\n");
return 0;
}
pid = fork();
if(pid < 0) {
fprintf(stderr,"Fail to create process!\n");
return 0;
}
else if(pid > 0) {//父进程
close(fd[0]);//关闭读管道,使得父进程只能向管道写入数据
write(fd[1],buf,strlen(buf));
close(fd[1]);
}
else {//子进程
close(fd[1]);//关闭写管道,使得子进程只能从管道读取数据
read(fd[0],line,64);
printf("DATA FROM Parent: %s",line);
close(fd[0]);
}
return 0;
}
2.消息队列
消息队列可以边发送边接受。数据被分为一个个的数据单元(消息体)。
缺点是:1.每个消息体有一个最大长度的限制。队列所包含的消息总长度也是有上限的。2.消息队列通信过程中存在用户态和内存态之间数据拷贝问题。进程往消息队列写入数据时,会发送用户拷贝数据到内核态的进程,读取的时候回从内核态拷贝数据到用户态。
zydz@zydz-virtual-machine:/mnt/hgfs/sd/file_test$ cat /proc/sys/kernel/msgmnb
16384
键值构建ftok
#include <sys/types.h>
#inlcude <sys.ipc.h>
key_t fotk(const char *pathname,int proj_id);
/*ftok函数将路径名和项目的表示符转变成一个系统V的IPC键值*/
获取消息msgget()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key,int msgflg);
/*key 用来与内核中其他消息的现有关键字值相比较*/
/*msgflag 比较之后,打开或者访问操作依赖于msgflag参数的内容*/
msgsnd()发送消息
#include <sys/type.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid,const void *msgp,size_t msgsz,int msgflg);
/*msgid 队列标识符 msgp消息缓冲区指针 msgsz消息大小 msgflg IPC_NOWAIT表示不阻塞)*/
msgrcv()接受函数
#include <sys/type.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msgid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
//mtype指定从队列中获取的消息类型。
msgctl()函数
#include <sys/type.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid,int cmd,struct msqid_ds *buf);
//向内核发送一个cmd命令,内核根据此来判断进行何种操作。 cmd 的取值可以是IPC_STAT获取msqid_ds结构 IPC_SET 这是msqid_ds结构的ipc_perm成员值 IPC_RMID:内核删除队列
3.共享内存
共享内存解决了消息队列存在的内核态和用户态之间数据拷贝的问题。
现代操作系统对于内存管理采用的是虚拟内存技术,也就是说每个进程都有自己的虚拟内存空间,虚拟内存映射到真实的物理内存。共享内存的机制是不同的进程拿出一块虚拟内存空间,映射到相同的物理内存空间。这样一个进程写入的东西,另外一个进程马上就能看到,不需要拷贝。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,size_t size,int shmflg);//创建共享内存 shmflg参数是内存的操作方式,有读或者写两种
void *shmat(int shmid,const void *shmaddr,int shmflg);//获得一个共享ID对应的内存的起始地址,shmaddr指定了共享内存的地址,如果参数为0则是让系统决定。
int shmdt(const void *shmaddr);//从程序中分离一块共享内存
4.信号量
当使用共享内存的通信方式,如果有多个进程同时往共享内存里面写入数据,有可能先写的数据的内容被其他进程覆盖了。
因此需要一种保护机制,信号量本质上是一个整形的计数器,用于实现进程间的互斥和同步。信号量代表着资源的数量,操作信号量的方式有两种:
1)P操作 信号量减一,<0,说明资源被占用。需要阻塞等待。>=0,进程正常执行
2)V操作 信号量加1,<0,有进程阻塞。>0,当前没有阻塞的进程。
信号量初始值为1;
使用信号量可以实现互斥和同步
信号量数据结构
union semun { /*信号量操作的联合结构*/
int val; /*整型变量*/
struct semid_ds *buf;/*semid_ds结构指针*/
unsigned short *array;/*数组类型*/
struct seminfo *_buf;/*信号量内部结构*/
};
新建信号量函数semget()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int segment(key_t key,int nsems,int semflg);//semflg是打开信号量的方式IPC_CREAT IPC_EXCL
信号量操作函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid,struct sembuf *sops,unsigned nsops);//semops指向要在信号量集合上执行操作的一个数组,sops是数组中操作的个数
struct sembuf{
ushort sem_num;//信号量的编号
short sem_op;//信号量的操作
short sem_flg;//信号量的操作标志
/*如果sem_op是负,从信号量减掉一个值,如果是正,加上一个值*/
}
控制信号量参数semctl()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid,int semnum,int cmd,...);
sem案例
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include "errno.h"
typedef int sem_t;
extern int errno;
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
} arg;
sem_t CreateSem(key_t key,int value)/*建立信号量,魔数key和信号量的初始值value*/
{
union semun sem;
sem_t semid;
sem.val = value;
printf("create\n");
semid = semget(key,1,IPC_CREAT|0666);
if (-1 == semid)
{
//printf("create semaphore error[%d:%s]!\n",errno,strerror(errno));/*打印信息*/
printf("create semaphore error!%s\n",strerror(errno));
return -1;
}
semctl(semid,0,SETVAL,sem);
return semid;
}
int Sem_P(sem_t semid)
{
printf("p\n");
struct sembuf sops = {0,+1,IPC_NOWAIT};
return (semop(semid,&sops,1));//对信号量进行P操作
}
int Sem_V(sem_t semid)
{
printf("v\n");
struct sembuf sops = {0,-1,IPC_NOWAIT};
return (semop(semid,&sops,1));//对信号量进行V操作
}
void SetvalueSem(sem_t semid,int value)
{
union semun sem;
sem.val = value;
semctl(semid,0,SETVAL,sem);//设置信号量的值
}
int GetvalueSem(sem_t semid)
{
union semun sem;
return semctl(semid,0,GETVAL,sem);//获取信号量的值
}
void DestroySem(sem_t semid)
{
union semun sem;
sem.val = 0;
printf("destroy\n");
semctl(semid,0,IPC_RMID,sem);
}
int main(void)
{
key_t key;
int semid;
char i;
int value = 0;
key = ftok("/mnt",'b');
//key = 500;
printf("key = %d\n",key);
semid = CreateSem(key,10);
for (i = 0;i <= 3;i++){
Sem_P(semid);
Sem_V(semid);
}
value = GetvalueSem(semid);
printf("信号量值为:%d\n",value);
DestroySem(semid);
return 0;
}
5.Socket通信
LINUX下的线程
用线程来实现相同的功能有以下优点:
- 系统资源消耗低
- 速度快
- 线程间的数据共享比进程间容易得多
创建函数pthread_create()
#include <pthread.h>
int pthread_create(pthread_t thread,pthread_attr_t *attr,void *(*start_routine)(void*),void *arg);
线程的结束函数
pthread_join()和pthread_exit()
extern int pthread_join __p((pthread_t __th,void **__thread_return));
extern void pthread_exit __P((void *__retval)) __attribute__((__noreturn__));
案例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
static int run = 1;
static int retvalue;
void *start_routine(void *arg)
{
int *running = arg;
printf("子线程初始化完毕,传入参数:%d\n",*running);
while(*running)
{
printf("子线程运行\n");
usleep(1);
}
printf("子线程退出\n");
retvalue = 8;
pthread_exit((void*) &retvalue);
}
int main(void)
{
pthread_t pt;
int ret = -1;
int times = 3;
int i = 0;
int *ret_join = NULL;
ret = pthread_create(&pt,NULL,(void *)start_routine,&run);
if (ret != 0)
{
printf("建立线程失败\n");
return 1;
}
usleep(1);
for(;i<times;i++)
{
printf("主线程打印\n");
usleep(1);
}
run = 0;
pthread_join(pt,(void*)&ret_join);
printf("线程返回值为:%d\n",*ret_join);
return 0;
}
gcc -o thread thread.c -lpthread
线程间的互斥
互斥初始化方式宏定义
#include <pthread.h>
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_INITIALIZER_NP;
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
互斥的初始化函数
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_muextattr_t *mutexattr);
互斥的锁定函数
int pthread_mutex_lock(pthread_mutex *mutex);
互斥的预锁定函数
int pthread_mutex_trylock(pthread_mutex *mutex);
互斥的解锁函数
int pthread_mutex_unlock(pthread_mutex *mutex);
互斥的销毁函数
int pthread_mutex_destroy(pthread_mutex *mutex);
线程中使用信号量
#include <semaphore.h>
extern int sem_init __P((sem_t __sem,int __pshared,unsigned int __value));
int sem_post(sem_t *sem);//增加信号量的值
int sem_wait(sem_t *sem);//减少信号量的值,如果信号量的值小于0,会一直阻塞到信号量的值大0
int sem_destroy(sem_t *sem);
UDP
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#define PORT_SERV 8888
#define BUFF_LEN 256
void static udpserv_echo(int s,struct sockaddr* client)
{
int n;
char buff[BUFF_LEN];
socklen_t len;
while(1)
{
len = sizeof(*client);
n = recvfrom(s,buff,BUFF_LEN,0,client,&len);
sendto(s,buff,n,0,client,len);
}
}
int main(int argc,char* argv[])
{
int s;
struct sockaddr_in addr_serv,addr_client;
s = socket(AF_INET,SOCK_DGRAM,0);;
memset(&addr_serv,0,sizeof(addr_serv));
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr.s_addr = hton1(INADDR_ANY);
addr_serv.sin_port = htons(PORT_SERV);
bind(s,(struct sockaddr*)&addr_serv,sizeof(addr_serv));
udpserv_echo(s,(struct sockaddr*)&addr_client);
return 0;
}
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#define PORT_SERV 8888
#define BUFF_LEN 256
static void udpclient_echo(int s,struct sockaddr *to)
{
char buff[BUFF_LEN] = "UDP_TEST";
struct sockaddr_in from;
socklen_t len = sizeof(*to);
sendto(s,buff,BUFF_LEN,0,to,len);
recvfrom(s,buff,BUFF_LEN,0,(struct sockaddr*)&from,&len);
printf("recieved:%s",buff);
}
int main(int argc,char* argv[])
{
int s;
struct sockaddr_in addr_serv;
s = socket(AF_INET,SOCK_DGRAM,0);
memset(&add_serv,0,sizeof(addr_serv));
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);
addr_serv.sin_port = htons(PORT_SERV);
udpclient_echo(s,(struct sockaddr*)&addr_serv);
close(s);
return 0;
}
Linux用户层网络编程
套接字
套接字地址结构
不同类型的协议有不同的套接字地址结构,但是都可以和原始的套接字地址结构转换。
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
struct sockaddr_in{
u8 sin_len;
u8 sin_family;
u16 sin_port;
struct in_addr sin_addr;
char sin_zero[8];
}
socket
int socket(int domain,int type,int protocol);
bind
int bind(int sockfd,const struct sockaddr *my_addr,socklen_t addrlen );
listen
int listen(int sockfd,int backlog);
accept
int accept(int sockfd,struct sockaddr* client_addr,socklen_t addrlen);
connect()
int connect(int sockfd,struct sockaddr *,int addrlen);
write()
size_t write(int sockfd,char *buf,int len);
read()
size_t write(int sockfd,char *buf,int len);
close()
int close(int sockfd);
信号处理
在linux操作系统中当某些状态发生变化时,系统会向相关的进程发送信号。信号的处理方式是系统会先调用进程中注册的处理函数,然后调用系统默认的响应方式,包括终止进程。因此在系统进程结束前,注册信号函数进行一些处理是一个完善程序的必备条件。
信号是发生某件事情时的一个通知,有时候也将其称做软中断。信号将时间发送给相关的进程。相关进程对信号进行捕捉和处理。
信号函数的注册通过signal()完成。
#include <signal.h>
typedef void (*sighandler_t)(int)
sighandler_t signal(int signum,sighandler_t handler);//给信号signum注册一个void(*sighandler_t)(int)类型的函数。
//信号SIGPIPE 如果正在写入套接字的时候,读取端已经关闭,可以得到一个SIGPIPE信号。
//信号SIGINT 当CTRL+C终止进程时,或者kill命令发送时,会有SIGINT信号。用户终止进程运行给当前活动的进程发送这个信号。
字节序转换
字节序转换函数
#include <arpa/inet.h>
uint32_t htonl(uint32_t h)
uint16_t htons(uint32_t h)
uint32_t ntohl(uint32_t h)
uint16_t ntohs(uint16_t h)
字符串IP地址和二进制IP地址转换
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp,struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_addr(const char *cp);
char inet_ntoa(struct in_addr in);//二进制地址转成字符串地址
struct in_addr inet_makeaddr(int net,int host);//合成IP地址
in_addr_t inet_lnaof(struct in_addr in);//获得地址的主机部分
in_addr_t inet_netof(struct in_addr in);//获得地址的网络部分
安全的地址转换
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int inet_pton(int af,const char *src,void *dst);
const char *inet_ntop(int af,const void *src,char *dst,socklen_t cnt);
判定是否为套接字描述符
int issockettype(int fd)
{
struct stat st;
int err = fstat(fd,&st);
if (err < 0)
{
return -1;
}
if (st.st_mode & s_IFMT) == S_IFSOCK)//判断是否是套接字描述
{
return 1;
}
else
{
return 0;
}
}
获取主机的信息(不可重入)
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr,int len,int type);
struct hostent
{
char *h_name;//主机的正式名称
char **h_aliases;//别名列表
int h_addrtype;//主机地址类型
int h_length;//地址长度
char **h_addr_list; //地址列表
}
获取协议名称 P230
进行一些属性的修改
内核sock结构
struct socket {
socket_state state;//socket状态(SS_CONNECTED等)
unsigned long flags;//socket 标志(SOCK_ASYNC_NOSPACE等)
const struct proto_ops *ops;//协议特定的socket操作
struct fasync_struct *fasync_list;//异步唤醒列表
struct file *file;//文件指针
struct sock *sk;//内部网络协议结构
wait_queue_head_t wait;//多用户的等待时间
short type;//socket 类型(SOCK_STREAM等)
获取和设置套接字选项getsocketopt()/setsocketopt()
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int s,int level,int optname,void *optval,socklen_t *optlen);
int setsockopt(int s,int level,int optname,void *optval,socklen_t optlen);
//level是所在的协议层 SOL_SOCKET/IPPROTO_IP/IPPROTO_TCP
//optname optvalue的表格在P330
optval结构
#include <netinet/tcp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
typedef union optval{
int val;
struct linger linger;
struct timeval tv;
unsigned char str[16];
}val;
static val optval;
typedef num valtype{
VALINI,
VALLINGER,
VALTIMEVAL,
VALUCHAR,
VALMAX //错误类型
}valtype;
ioctl()
fcntl()
UDP协议程序设计中的几个问题(待补充)
使用SOCK_PACKET抓包例程(待补充)
使用SOCK_PACKET编写ARP请求(待补充)
原始套接字
为什么要使用原始套接字?
使用标准套接字可以解决大部分的原始,应用层的需求基本上覆盖在标准套接字之上。但是考虑一些深层的问题时,跨出TCP/IP的标准,或者需要监听网卡的时候,就需要用SOCKET_RAW
原始套接字主要应用在底层网络上,原始套接字与IP层级网络协议栈核心打交道。
原始套接字提供了3种标准套接字不具备的功能。
- 使用原始套接字可以读、写ICMP/IGMP分组。
- 使用原始套接字可以读写特殊的IP数据报,内核不处理这些数据报的协议字段。内核只处理1(ICMP)、2(IGMP)、16(TCP)、17(UDP)
- 使用原始套接字,利用setsocketopt()设置套接字选项,使用IP_HDRINGCL可以对IP头部进行操作,因此可以修改IP数据和IP层之上的各层数据,构造自己的特定类型的TCP或者UDP的分组。
创建原始套接字
int rawsock = socket(AF_INET,SOCK_RAW,protocol);
//protocol 一般情况下不能设置为0; IPPROTO_IP IPPROTO_ICMP IPPROTO_TCP IPPROTO_UDP IPPROTO_RAW:原始IP报
IP_HDRINCL套接字选项
int set = 1;
if(setsockopt(rawsock,IPPROTO_IP,IP_HDRINCL,&set,sizeof(set))<0){/*错误处理*/}
不需要bind,直接用sendto 和recvfrom
sendto(rawsock,data,datasize,0,(struct sockaddr*)&to,sizeof(to));
recvfrom(rawsock,data,size,0,(struct sockaddr)&from,&len);
void wave_flag_write(char *sendbuf,u32 len)
{
u32 offset = 0x700000;
printk("wave flag write test\n");
qspi_flash_read(offset,len);
qspi_erase(flash[0],offset,ERASE_LEN);
qspi_flash_read(offset,len);
qspi_write(flash[0],offset,len,sendbuf);
qspi_flash_read(offset,len);
}
void wave_flag_read()
{
u32 offset = 0x700000;
qspi_flash_read(offset,1024);
}