linux系统编程

linux系统编程


文章目录

  • linux系统编程
    • linux
      • 概述
      • 环境搭建
      • VIM编辑器常用指令
      • GDB调试工具
    • 文件
      • 打开与关闭文件
      • 读写字节
      • 文件描述符
      • 动态与静态文件
      • main函数的参数
      • 读写结构体
      • 用标准c库读写文件
    • linux进程
      • 程序与进程
      • 进程标识符
      • 父子进程
      • 内存分配
      • 创建进程
      • 结束进程
      • 进程退出状态
      • EXEC族函数
      • system函数
      • popen函数
    • linux线程
      • 概述
      • 相关API
      • 线程
      • 互斥锁
      • 线程死锁
      • 条件
    • 进程通信
      • 无名管道通信(PIPE)
        • 特点
        • 创建与关闭
        • 使用方法
      • 有名管道通信(FIFO)
        • 特点
        • 创建与关闭
        • 使用方法
      • 消息队列(msg)
        • 特点
        • 创建
        • 使用方法
        • 键值生成
      • 共享内存(shm)
        • 特点
        • 创建
        • 使用方法
      • 信号(signal)
        • 概述
        • 信号处理函数的注册
        • 信号发送函数
        • 带消息的信号
      • 信号量(semaphore)
        • 概述
        • 创建与控制信号量
        • 获取和释放锁
        • 使用方法
    • 网络编程
      • 通信协议
      • 字节序
      • API
      • Socket服务器
      • Socket客户端
    • makefile
  • Tools
      • Typora

linux

概述

1.免费使用和自由传播的类Unix操作系统

2.版本

​ 发行版: ubuntu <–嵌入式开发

​ cent os <–web服务器

​ 红旗linux

​ Linux内核:发行版基于Linux内核进行二次开发

​ IOS

​ Android

环境搭建

虚拟机:为一个设备同一时间运行多个操作系统提供可能 vmware

真机:公司的研发服务器

VIM编辑器常用指令

h、j、k、l 移动光标

x 删除一个字符

dd 删除一行

u 撤销

Ctrl + r REDO

p 粘贴

y 复制(需选中)

yy 复制一行

/XXX 查找XXX n下一个 N上一个

:n 跳转到n行

:wq 保存并退出

:q! 强制退出(不保存)

GDB调试工具

要使用GDB来调试程序,编译时要加入 -g 选项

gdb a.out 进入GDB调试

set args 1 2 3 向可执行文件输入参数1 2 3

list n 从第n行开始列出源代码

b n 在第n行设置断点

delete n 删除断点

run 运行程序,遇到断点会停止,命令后可加传入参数

display 显示变量值

set x=6 将x设置为6

continue 继续运行

next 下一行

step 进入函数

finish 退出函数

print 打印变量,表达式,函数 如:print var print 2+3 print sum(3)

q 退出GDB

文件

打开与关闭文件

#include
#include
#include
/*
返回int作为文件描述符,来表示所打开的文件,失败返回-1
pathname:路径	缺省则按照当前路径,如 ./fileName
flags:文件访问权限,如O_RDONLY | O_WRONLY | O_RDWR
	其中O_CREAT(若不存在则创建) | O_APPEND(追加) | O_EXCL(与O_CREAT一起检测文件是否存在) | 				O_TRUNC(文件存在且可写将截断为0,重新开始写)为可选项,用和上述三个按位|来运算
mode:一定在open中存在文件被创建的可能性时,需要给新文件一个权限flag,如0600
	给文件所有者的权限,文件权限类型:可读 4 可写 2 执行 1,系统在文件创建后,并不在该次打开文件时检查权限
命令行下可 ls -l 查看文件夹下文件详细信息
以下函数出错返回-1.并将errno设为一个合适的错误值
*/
int open (const char* pathname, int flags);
int open (const char* pathname, int flags, mode_t mode);
int creat (const char* pathname, mode_t mode);
//creat(确实少了e,但就是这样,哈哈)相当于第二个参数被指定为O_WRONLY|O_CREAT|O_TRUNC的open()函数

读写字节

#include
/*
从当前文件fd的内容指针开始最多写count到缓冲区buf中,并返回实际读入的字节数,没有数据读时将进入阻塞
将缓冲区buf的字节写count到文件fd中,并返回写入的字节数
buf可以是任意的数据类型,如整型,结构体
失败返回-1,并修改errno
*/
ssize_t read (int fd, const void* buf, size_t count);
ssize_t write (int fd, const void* buf, size_t count);
//当以APPEND写时,即使有多个写者,但每次写时都会在文件末尾来写
//回写:write被调用后,内核立刻将数据放入缓冲区,并返回结果,实际上此时可能并没有写入磁盘,而是在后台将多个这样的数据排好序,在空闲时依次写入,但不影响立刻read,因为它会从缓冲区中获得数据
//当然调用fsync(int fd)函数后,可以保证fd对应的数据被回写,在驱动器确认之前,函数不会返回
/*成功返回0,失败返回-1,并修改errno*/
int close (int fd);

#include
#include
/*
对于fd中,将读写光标相对于whence(SEEK_SET、SET_END、SEEK_CUR)偏移offset(正数向后,反之向前),返回光标距离开头的字节数,失败返回-1,并修改errno
*/
off_t lseek (int fd, off_t offset, int whence);

文件描述符

linux默认有文件描述符:0(标准输入) 1(标准输出) 2(标准错误)

每个进程至少有上述3个打开的文件描述符,除非显示关闭

c库中它们被宏定义 STDIN_FILENO STDOUT_FILENO STDERR_FILENO

文件描述符不仅用于普通文件访问,也用于访问设备文件,管道,FIFO,套接字等

​ 一切皆文件,读写的东西均可用文件描述符来访问

read (0,buffer,5);//从终端即键盘读入5个字节放入buffer
write (1,buffer,5);//从buffer输出5个字节到shell即标准输出

文件描述符只在当前的进程使用,用于识别哪一个文件,非本进程的文件标识符是没有意义的

打开文件后要关闭文件,防止文件的丢失

动态与静态文件

文件存在磁盘,此时的文件成为静态文件

而文件被进程打开后,Linux内核将file读入内存,并由结构体来记录文件信息,此时文件为动态文件,对文件的操作为动态文件,close后,磁盘文件被更新

硬盘以块存储,内存中以字节存储

main函数的参数

int main (int arge, char **argv) {
     
    /*
    argv表示从终端读入的字符串个数
    argv是指向指向char*即字符串的指针,用来存储各个字符串
    argv[0] argv[1] argv[2] 可依次获取字符串
    如:在命令行输入如 ./a.out char1 char2
    */
    return 0;
}

读写结构体

Data* temp = (Data*)malloc(sizeof(Data));
Data data = {
     10,"hello"};
int fd = open("data",O_RDWR|O_CREAT|O_TRUNC,0600);
write(fd,&data,sizeof(Data));
lseek(fd,0,SEEK_SET);
read(fd,temp,sizeof(Data));
printf("%d\t%s\n",temp->num,temp->s);
close(fd);

用标准c库读写文件

open:是unix系统调用函数(包括linux),返回文件描述符,它是在文件描述符表里的索引

fopen:是ANSIC标准中的C语言函数,不同系统中会调用不同的API,返回指向文件结构的指针

fopen因此具有良好的执行性,更接近系统内核,open只能UNIX系统下使用,比如在WINDOWS下无法使用

#include
/*
一个打开的文件称作一个流
*/
FILE* fopen (const char* path, const char* mode);
FILE* fdopen (int fd, const char* mode);
//失败将返回NULL,fdopen函数用于将一个文件描述符与一个流关联(mode必须与原fd权限匹配)
int fileno (FILE* stream);
//获取流的文件描述符,失败返回-1
int fclose (FILE* stream);
//成功返回0,失败返回-1,并修改errno
int fseek (FILE* stream, long offset, int whence);
//成功返回0,失败返回-1,并修改errno
int fputc (int c, FILE* stream);
int fgetc (int c,FILE* stream);
long ftell(FILE* stream);
//返回当前位置,成功返回0,失败返回-1,并修改errno
void rewind (FILE* stream);
//重置到流的初始位置
int feof (FILE* stream);	
//到达文件尾后返回值为1,否则为0
/*
fwrite返回写的元素个数,即第三个参数,并非字节数,而fread是实际所读的元素个数,若给参数大于实际,则返回实际读取元素个数
以下函数一般用于读写二进制文件,比如结构体
*/
size_t fread (const void *ptr, size_t size, size_t nmenb, FILE* stream);
size_t fwrite (const void *ptr, size_t size, size_t nmenb, FILE* stream);

//以上产生的缓冲区是由c函数库来维护的,在用户缓冲区,并不在内核缓冲区

linux进程

程序与进程

程序是静态的概念,磁盘中的可执行文件为程序,而进程为程序运行时的动态概念

命令行下

ps -aux :查看当前设备所有进程

ps -aux|grep name :查看带name的进程来过滤

top :展示任务管理器

进程标识符

与文件标识符类似,每一个进程都有一个非负整数表示唯一的ID,叫做pid(linux下通常是C语言的int类型)

pid=0:交换进程:进程调度

pid=1:init进程:系统初始化

#include 
#include 
pid_t getpid (void);
//获取当前进程的pid
pid_t getppid (void);
//获取当前进程父进程的pid

父子进程

即A进程创建了进程B

每一个进程都是由其他进程创建的(除了init进程),因此每一个子进程都有一个父进程

内存分配

低地址------------------------------------------------->高地址

正文 初始化的数据 未初始化的数据 堆 栈 命令行和环境变量

如:[代码段] [初始化过的变量] [函数外未初始化的数据] [用户申请的空间] [函数调用时, 产生的内存] [argc.argv]

创建进程

/*
创建一个和当前进程印像一样的进程
返回2次值,一次返回到父进程,一次返回子进程
父进程中的pid为子进程的ID号
子进程中的pid为0
-1表示内部错误,并设置errno
*/
#include 
#include 
pid_t pid = fork (void);

子进程会共享代码即内存并在修改内存时选择性复制内存的存储(写时复制)来执行,较早的 linux 会直接全部复制

进程变量的修改是独立的,不会相互影响,创建进程后父子进程将一起同时运行

/*
vfork也可创建进程
区别有2:vfork产生的子进程会与父进程共享内存,父进程会被挂起进入等待,等待子进程运行结束或运行了一个新的可执行文件映像(如调用exit(0)时),父进程才继续运行
*/

结束进程

正常退出

#include 
/*
除了使用return外,exit函数可以用于结束进程
*/
exit (int status);	
//冲刷缓冲区等操作后调用_exit	status可用宏 EXIT_SUCCESS 和 EXIT_FAILURE
/*
exit函数在关闭流,删除临时文件后,会调用_exit函数,让内核对进程做清理,如释放内存关闭文件
*/
_exit (int status);	
//用vfork()产生的子进程必须用这个函数来终止进程,而不是exit()
_Exit (int status);	
//和_Exit()功能一样

非正常退出

#include 
abort (void);
//ctrl+c

进程终止时,内核会执行一段代码,用于释放内存,关闭文件等

进程退出状态

不对子进程退出状态进行收集,该子进程会成为僵死进程,它们只保留最小的概要信息

只要父进程获取了子进程的信息,子进程就会消失,否则将保持僵死状态

收集子进程退出状态:

/*
父进程在调用下面函数后会进入阻塞等待子进程结束并收集其状态
返回已经终止的子进程pid或者返回-1表示出错
子进程如果终止,它将立即返回子进程的pid
*/
#include 
#include 
pid_t wait (int *status);
//传入指针为NULL,则父进程不关心子进程的状态
//传入一个指向int的指针,exit()的参数值将放入该地址,在地址的数值可在父进程中读取,需要带参数的宏WEXITSTATUS(status)来解析
/*
当有多个进程时,我们只需要等待一个特定的进程终止后返回状态
参数pid是要等待进程的pid(fork函数调用的返回值为子进程的pid),当传入-1时表示任意子进程
参数status和wait()用法是一致的
options: WCONTINUED		WNOHANG(主进程不阻塞)		WUNTRACED	按位运算或0
如果执行成功则返回子进程的pid ,错误返回-1,并修改errno
*/
#include 
#include 
pid_t waitpid (pid_t pid, int *status, int options);

父进程不等待子进程而推出后,子进程便成为孤儿进程

linux为避免父进程不等待和提前结束而造成的僵死进程,init进程(pid=1)将作为这些进程的父进程,因为每一个进程的结束,都会引发内核来遍历它的子进程

EXEC族函数

/*
在调用的进程内执行一个可执行文件(二进制文件或任何可执行的脚本)
执行成功后原程序将不再执行,而是执行所调用的程序,这是因为函数会调用path中的映像载入内存来替换当前进程

path&file:路径	arg&argv[]:依次为所带参数,第一个必须为执行文件名字,然后为参数,必须以NULL结尾
执行失败返回-1,然后从原程序进行往下执行,并修该errno的值	TIPS:perror(const char* string);可打印失败原因
*/
#include 
int execl (const char* path, const char* arg, ...);	//含l结尾的函数参数arg以一系列字符串依次给出(向量)
int execlp (const char* file, const char* arg ...);	//含p结尾的函数将会从环境变量中寻找该文件
int execv (const char* path, char* const argv[]);	//含v结尾的函数参数arg以字符串数组给出
int execvp (const char* file, char* const argv[]);

一般与fork()配合使用

system函数

用于执行shell命令,实质是封装后的exec,与之不同是是它执行时,原进程会等待它结束,完毕后会继续执行原进程

/*
调用/bin/sh失败返回127,其他原因失败返回-1
*/
#include
int system (const char* string);

popen函数

/*
可以通过文件流获取运行的输出结果,而非直接输出到shell
command:指向一个以NULL结束的shell命令字符串指针
mode:r或w	返回的文件指针链接到command的标准输入或输出
返回的文件流可用fread来读取	只能用pclose()结束进程
*/
FILE* popen (const char* command, const char* mode);

linux线程

概述

典型的UNIX/LInux进程可以看成只有一个控制线程,一个进程在同一时刻只做一件事,而有了多个控制线程,一个进程在同一时间就可做不止一件事

进程是线程的容器,是分配系统资源的基本单位,其中有多个线程,并共享一块内存(有自己的栈),任意线程调用exit()后进程将会结束,同时,所有线程均结束运行

多个进程独立,互不影响,但资源耗费大,效率略低,对并发操作多线程更合适,通信也跟方便,而多个线程当一个线程出现问题,整个进程将会崩溃

相关API

/*
linux平台有成熟的pthread库支持,它是第三方库
*/



#include 
/*
将线程id放入thread所指向的内存,当attr为NULL时,线程属性为默认;start_routine是要执行的函数指针,该函数参数用指向参数的arg指针传递(传递多个用结构体)
成功返回0
*/
int pthread_create (pthread_t* thread, const pthread_attr_t* attr, void*(*start_routine)(void*), void * arg);
//线程的属性是一个结构体,它在头文件pthreadtypes.h中被定义
typedef struct _pthread_attr_s{
     
      int _detachstate;					//线程的分离状态
      int _schedpolicy;					//线程调度优先级
      struct sched_param _schedparam;	//线程的调度参数
      int _inheritsched;					//线程的继承性
      int _scope;						//线程的作用域
      size_t _guardsize;					//线程栈末尾的警戒缓冲区大小
      int _stackaddr_set;				//运行栈
      void* _stackaddr;					//线程栈的地址
      size_t _stacksize;					//线程栈的大小
}_pthread_attr_t;
//对于传入函数的结构体指针不能直接设置,而是借助于pthread_attr_init(),它的调用在这个函数前
//下面是设置优先级的例子
#include 
#include 
#include 		//线程优先级存放在结构中,需要这个头文件,因为该结构在此头文件中被定义
pthread_attr_t attr;	//需要传入线程创建函数的属性结构
struct sched_param sch;	//存放优先级的结构
pthread_t pt;			//用于未来存放线程的id
pthread_attr_init(&attr);	//初始化属性设置
sch.sched_priority = 256;	//设置存放优先级结构中的优先级信息为256
pthread_attr_setschedparam (&attr, &sch);  //在sch中取出优先级信息,放入要传入创建线程函数的attr属性结体中
pthread_create (&pt, &attr, (void*)start_routine, NULL);	//创建属性为指向attr结构体信息的线程
//把优先级放在结构中,再将优先级取出来,然后再设置属性结构体参数,这是对复杂结构的操作方式,目的是为了我避免直接操作复杂结构体而出现不可预料的麻烦

/*结束当前线程*/
/*它需要一个指针(因该指向一个static的变量),用于返回给调用pthread_join函数的线程捕获*/
void pthread_exit (void* _retval);

/*
调用thread_join的线程进入等待,直到thread线程调用pthread_exit或执行结束才返回并且回收被等待线程资源
成功返回0
*/
int pthread_join (pthread_t thread, void** _thread_return);
//_thread_return是一个指向指针的指针,它指向调用pthread_exit函数的指针参数,通常用一个指针变量的地址来表示

/*
获取当前线程id
*/
pthread_t pthread_self (void);

/*
判断是否为同一个线程
相等返回非0,否则为0
*/
int pthread_equal (pthread_t tid1,pthread_t tid2);



#include 
/*
互斥量本质是一把锁,在资源访问前对其进行加锁,访问后释放互斥量上的锁
某线程获得锁后其他线程将不能操作,进入阻塞,直到该线程释放锁
若释放后,有多个线程需要加锁,则首先获取锁的线程开始执行,其他线程依然进入阻塞状态
设计时,所有的线程因该遵守相同的访问规则
*/
/*
创建属性为attr的互斥锁mutex
互斥锁将存放在这个变量中,当attr为NULL时,表示默认的属性
*/
int pthread_mutex_init (pthread_mutex_t* mutex, const pthread_mutexattr_t* attr);
//或静态初始化
pthread_mutex_t mutex = PTHREAD_COND_INITIALIZER;
/*
销毁mutex互斥锁
*/
int pthread_mutex_destroy (pthread_mutex_t* mutex);
/*
加锁
当某个线程获得锁后,便一直开始执行,直到解锁,这期间如果其他线程也执行此语句,企图获得锁,而锁被当前程序占用
欲得互斥锁的线程进入等待序列,等待此线程对锁的释放
*/
int pthread_mutex_lock (pthread_mutex_t* mutex);

/*
解锁
调用后释放锁,获得锁后的线程相对于其他想获得锁的线程将一直执行到此为止
*/
int pthread_mutex_unlock (pthread_mutex_t* mutex);



/*
有时,某个线程拿到了锁,但想当某个条件满足时再执行,而拿到锁后,条件不满足只能释放锁,之后再判断条件是否满足,这样会造成资源不必要的浪费

概述:pthread_cond_wait总和一个互斥锁结合使用。在调用pthread_cond_wait前要先获取锁。pthread_cond_wait函数执行时先自动释放指定的锁,然后该线程进入阻塞,等待条件变量的变化。当条件满足时,自动将指定的互斥量重新锁住

方式:mutex互斥锁必须是普通锁,且在调用pthread_cond_wait()前必须由本线程加锁。确保在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起时进入等待前解锁
在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应,因此调用pthread_cond_signal后要立刻释放互斥锁,因为pthread_cond_wait的最后一步是要将指定的互斥量重新锁住,如果pthread_cond_signal之后没有释放互斥锁,pthread_cond_wait仍然要阻塞。
*/
/*
创建条件
成功返回0,attr设为NULL时为默认属性
*/
int pthread_cond_init (pthread_cond_t* restrict cond, const pthread_condattr_t* restrict attr);
//静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
/*
销毁条件
*/
int pthread_cond_destroy (pthread_cond_t* cond);
/*
调用后,在线程将阻塞等待cond,并释放mutex互斥锁
成功返回0
*/
int pthread_cond_wait (pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex);
/*
唤醒一个调用pthread_cond_wait的线程,pthread_cond_broadcast唤醒所有线程
成功返回0
*/
int pthread_cond_signal (pthread_cond_t* cond);

线程

void* run (void* arg) {
     
	static const int flag = 10;
	printf("flag from main is %d\n",*((int*)arg));
	int i;
	for(i = 0; i<3; i++) {
     
		printf("this is t print,%ld\n",(unsigned long)pthread_self());
		sleep(1);
	}
	pthread_exit ((void*)&flag);
}

int main (int argc, char const *argv[])
{
     
	pthread_t t;
	int* p;
	int arg = 10;
	pthread_create (&t, NULL, run, (void*)&arg);
	pthread_join (t, (void**)&p);
	printf("msg from t is %d\n",*p);
	int i;
	for(i = 0; i<3; i++) {
     
		printf("this is main print,%ld\n",(unsigned long)pthread_self());
		sleep(1);
	}
	return 0;
}

互斥锁

#define count 3
int flag;
pthread_mutex_t mutex;

void* run1 (void* arg) {
     
	pthread_mutex_lock (&mutex);
	int i;
	for (i = 0; i < count; ++i)
	{
     
		printf("flag from run1 is %d\n",flag++);
	}
	
	pthread_mutex_unlock (&mutex);
}

void* run2 (void* arg) {
     
	pthread_mutex_lock (&mutex);
	int i;
	for (i = 0; i < count; ++i)
	{
     
		printf("flag from run2 is %d\n",flag++);
	}
	pthread_mutex_unlock (&mutex);
}

int main (int argc, char const *argv[])
{
     
	pthread_mutex_init (&mutex, NULL);
	pthread_t t1;
	pthread_create (&t1, NULL, run1, NULL);
	pthread_t t2;
	pthread_create (&t2, NULL, run2, NULL);
	sleep(5);
	pthread_mutex_destroy (&mutex);
	return 0;
}

线程死锁

2个及以上的锁的时候才会造成线程死锁,即互相想获得对方已经拥有的锁

条件

int a;
pthread_cond_t cond;
pthread_mutex_t mutex;

void* run1 (void* arg) {
     
	pthread_mutex_lock (&mutex);
	pthread_cond_wait (&cond, &mutex);
	printf("this is run1 print, run1 is end\n");
	pthread_mutex_unlock (&mutex);
}

void* run2 (void* arg) {
     
	int i;
	for ( i= 0; i < 5; i++) {
     
		pthread_mutex_lock (&mutex);
		printf("this is run2 print, a = %d\n",a++);
		if(a == 3){
     
			pthread_cond_signal (&cond);
		}
		pthread_mutex_unlock (&mutex);
		sleep(1);
	}

}

int main(int argc, char const *argv[])
{
     
	pthread_mutex_init (&mutex, NULL);
	pthread_cond_init (&cond, NULL);
	pthread_t t1;
	pthread_create (&t1, NULL, run1, NULL);
	pthread_t t2;
	pthread_create (&t2, NULL, run2, NULL);
	pthread_join (t1, NULL);
	pthread_join (t2, NULL);
	pthread_mutex_destroy (&mutex);
	pthread_cond_destroy (&cond);
	return 0;
}
/*生产者与消费者	from CSDN*/
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 


#define ERR_EXIT(m) \
		do{ \
			perror(m); \
			exit(EXIT_FAILURE); \
		}while(0)

#define CONSUMERS_COUNT 2 //消费者数量
#define PRODUCERS_COUNT 1 //生产者数量
//互斥锁与条件变量使用可以提供一个无界缓冲区


pthread_mutex_t g_mutex;//互斥量
pthread_cond_t g_cond;//条件变量
pthread_t g_thread[CONSUMERS_COUNT+PRODUCERS_COUNT];//线程组

int nready = 0;//表示当前产品数量,即保护变量
void* consume(void* arg)
{
     
	int num = (int)arg;
	
	while(1)
	{
     
		/******条件变量使用规范********/
		/******使条件等待线程等待信号********/
		pthread_mutex_lock(&g_mutex);//加锁
		while(nready==0)//等待一个假条件,条件为真不再等待
		{
     
			printf("%d begin wait a condtion...\n",num);
			pthread_cond_wait(&g_cond, &g_mutex);
		}
		printf("%d end wait a condtion...\n", num);//结束等待,可以消费产品
		printf("%d begin consume product\n", num);
		--nready;
		printf("%d end consume product\n", num);
		pthread_mutex_unlock(&g_mutex);//解锁
		/******条件变量使用规范********/
		sleep(1);
	}
	
	return NULL;
}
void* produce(void* arg)
{
     
	int num = (int)arg;
	while(1)
	{
     
		/******条件变量使用规范********/
		/******给条件等待线程发送信号********/
		pthread_mutex_lock(&g_mutex);//加锁

		printf("%d begin produce product\n", num);
		++nready;
		printf("%d end produce product\n", num);

		pthread_con_signal(&g_cond);//
		printf("%d signal\n", num);//num生产线程通知消费线程
		pthread_mutex_unlock(&g_mutex);//解锁
		/******条件变量使用规范********/
		sleep(5);
	}
	return NULL;
}
int main(void)
{
     
	int i;
	
	pthread_mutex_init(&g_mutex, NULL);//初始化互斥量
	pthread_cond_init(&g_cond, NULL);//初始化条件变量
	
	//初始化线程
	for(i=0; i<CONSUMERS_COUNT; i++)
	{
     
		pthread_create(&g_thread[i], NULL, consume, (void*)i);
	}
	for(i=0; i<PRODUCERS_COUNT; i++)
	{
     
		pthread_create(&g_thread[i+CONSUMERS_COUNT], NULL, produce, (void*)i);
	}

	//等待线程回收
	for(i=0; i<PRODUCERS_COUNT+CONSUMERS_COUNT; i++)
	{
     
		pthread_join(&g_thread[i], NULL);
	}
	pthread_cond_destroy(&g_sem_full);//销毁条件变量
	
	pthread_mutex_destroy(&g_mutex);//销毁互斥锁

	return 0;
}

进程通信

进程间通信(IPC,InterProcess Communication):在不同进程之间传播或交换信息

IPC的方式:管道(无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Stream等(后两个支持不同主机上的两个进程IPC)

无名管道通信(PIPE)

特点

1.数据单方向,比如同一时间只能一个进程写一个进程读,不能同时读取,也称半双工管道

2.有“亲缘关系”的进程间,如父子进程

3.数据写入后,读取后便消失(不存储数据),只存在于内存中,由内核来管理

创建与关闭

#include
/*
失败返回-1,成功返回0,需要一个文件标识符数组
fd[0]读
fd[1]写
*/
int pipe (int fd[2]);
/*
关闭管道
*/
close(fp[1]);
close(fp[2]);

使用方法

int main (int arge, char* argv[]) {
     
	int fd[2];
	pipe(fd);
	int pid = fork();
	if(pid >0 ) {
     
		wait(NULL);
		close(fd[1]);	//要把不关心的管道端关掉
		char buf[128];
		read(fd[0],buf,128);	//用read和write来读写
		puts(buf);
	}else if (pid == 0) {
     
		close(fd[0]);
		write(fd[1],"hello",5);
	}else {
     
		puts("ERROR");
	}
	return 0;
}

有名管道通信(FIFO)

特点

它和无名管道一样,也是单向的通信

不同的是它可以和无关的进程间交换数据,并且它有路径名与之关联,它以一种特殊的设备文件形式存在与系统文件即磁盘内

创建与关闭

#include 
#include 
/*
mode与open函数的mode相同,一旦创建后,就可用一般的I/O来操作它
失败返回-1,成功返回0,perror()可打印原因	errno(记录系统错误码)==EEXIST
*/
int mkfifo (const char* pathname, mode_t mode);
/*
用open来打开这个文件,即链接到管道
当没有指定O_NONBLOCK(默认)时,只读open会进入阻塞直到某个其他进程为写而打开此FIFO,只写open会进入阻塞直到某个其他进程为读而打开此FIFO
用close来关闭这个文件
*/
close (fd)

使用方法

	int flag = mkfifo("./fifo", 0600);
	if(flag < 0 && errno != EEXIST) {
     	//当仅文件创建按失败时执行if语句内的内容
		perror("");						//加入与运算时因为如果文件存在flag的值将为-1
	}
	int fd = open("./fifo",O_RDONLY);	//链接到管道并进入阻塞,直到另一个进程为读而打开它
	printf("open sucessfully");
	while(1) {
     
		char* buf = (char*)malloc(128);
		read(fd,buf,128);
		//sleep(3);
		puts(buf);
		free(buf);
	}
//----------------
int main (int arge, char* argv[]) {
     
	int fd = open("./fifo",O_WRONLY);	//链接到管道并进入阻塞,直到另一个进程为写而打开它
	while(1) {
     
		char buf[128];
		gets(buf);
		write(fd, buf,sizeof(buf));
		puts(buf);
	}
}

消息队列(msg)

特点

消息队列,就是消息的链接表,存放在内核中,一个消息队列由一个标识符(队列ID)来标识

消息具有特定的格式和特定的优先级

数据被读取,进程被结束,消息列表内容不会被删除

可以随即查询,不一定要先进先出,可按消息类型读取

创建

#include 
#include 
#include 
/*
创建一个消息队列,key由ftok()得到	(#include  #include )
当flag是权限信息,当IPC_CREAT且没有与key对应的消息队列时,创建
成功返回ID,失败返回-1
*/
int msgget (key_t key,int flag);
/*
发送消息
为msqid队列上添加一个消息,ptr指向一个消息缓冲区,flag可设置为0表示忽略
成功返回0,失败返回-1
*/
int msgsnd (int msqid, const void *msgp, size_t size, int flag);
//msgp所指向的内存空间应该仿照以下结构
struct msgbuf {
     
	long mtype;       /* message type, must be > 0 */
	char mtext[1];    /* message data */
};
/*
接收消息
从msqid的队列中,获取类型为msgtyp的消息类型,并把它放入msgp所指向的内存,消息的结构大小(不含mtype)为msgsz,		flag为0时当队列中没有消息时,会等待消息的接收
成功返回消息数据长度,失败返回-1
*/
int msgrcv (int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);//type:消息类型
/*
控制消息队列
对队列进行cmd的命令
成功返回0,失败返回-1
*/
int msgctl (int msqid, int cmd, struct msqid_ds *buf);
msgctl (msgId, IPC_RMID,NULL);
//上述可移除消息队列ID为msgId的队列

使用方法

struct msgbuf {
     		//消息缓冲区的结构,可以根据实际情况加以修改,但mtype是一致的,且总大小不超过8192个字节
	long mtype;
	char mtext[128];
};

int main(int argc, char const *argv[]) {
     
	int msgId = msgget(ftok(".",2), IPC_CREAT|0777);
	struct msgbuf msgBuf = {
     111,"hello"};
//	while(1){
     
		msgsnd(msgId, &msgBuf, strlen(msgBuf.mtext),0);
		printf("msg is sending");
		sleep(10);
//	}
	return 0;
}
//---------------------------------------------------------------------------------------------------
struct msgbuf {
     
	long mtype;       /* message type, must be > 0 */
	char mtext[128];    /* message data */
};

int main(int argc, char const *argv[])
{
     
	int msgId = msgget(ftok(".",2), IPC_CREAT|0777);
	struct msgbuf msgBuf;
	msgrcv(msgId, &msgBuf, 128, 111, 0);
	printf("rcv is %s", msgBuf.mtext);
	return 0;
}

键值生成

#include 
#include 
key_t ftok (const char* fname, int id);//"."表示当前文件下

共享内存(shm)

特点

进程间共享一块物理内存空间的通信方式,它是IPC最快捷的方式

创建

#include 
#include 
/*
返回由ftok返回的key,大小为1024倍数的size,0666|IPC_CREAT不存在则创建读写权限的shmid,失败返回-1
*/
int shmget (Key_t key, size_t size, int flag);
/*
获得共享内存的地址,即链接到共享内存
addr为NULL时linux内核会自动分配内存,flag为0时表示可读可写
返回指向内存地址的指针
*/
void* shmat (int shm_id, const void* shmaddr, int shmflg);
/*
传入共享内存地址,函数将结束与进程的链接
注意参数时共享内存的地址,并非id
*/
int shmdt (void *shmaddr);
/*对shm_id的共享内存进行控制,操作为cmd*/
int shmcrl (int shm_id, int cmd, struct shmid_ds* buf);
//shmcrl(shmId, IPC_RMID, 0)来释放共享内存

命令行下ipcs -m可查看当前系统的共享内存ipcs -m id可删除共享内存

使用方法

	int shmId = shmget(ftok(".",1),1024,IPC_CREAT|0666);
	char* buf=(char*)shmat(shmId,NULL,0);
	strcpy(buf,"hello world");
	shmdt(buf);
	getchar();
	return 0;
//---------------------------------------------------------------------------------------------------
	int shmId = shmget(ftok(".",1),1024,IPC_CREAT|0666);
	char* buf=(char*)shmat(shmId,NULL,0);
	puts(buf);
	shmdt(buf);
	getchar();
	shmctl(shmId,IPC_RMID,NULL);
	return 0;

信号(signal)

概述

信号,为linux提供了一种处理异步事件的方法

​ 每个信号都有一个名字和编号,这些名字以SIG开头,信号定义在signal.h头文件中,信号名都定义为正整数

​ 如SIGQUIT SIGKILL SIGINT

命令行下,kill -l 来查看信号名字和编号(从1开始编号),如kill -9 pid 可结束进程

信号处理:忽略,捕捉,默认动作(SIGKILL SIGSTOP不允许忽略)

​ 常用的时信号捕捉:写一个信号处理函数,这个函数在内核被注册,当该信号产生时,内核将会调用它

​ 每一个信号系统都有默认的处理动作,当然你可以自定义这些动作,也可以在程序中发出某个信号

信号处理函数的注册

#include 
/*
函数参数为要捕捉的信号,以及指向handeler的函数指针,注册后,当有这种信号时,自定义的函数会被调用
可注册多个信号处理函数来捕捉多个信号
当handler为SIG_IGNSIG_IGN时,将会忽略这种信号
*/
sighandler_t signal (int signum, sighandler_t handler);
//handler函数因该是如下类型,因为函数名将作为指针变量传入上述函数
void handler (int signum) {
     
	//signum值为捕捉的信号编号
	//对SIGKILL	SIGSTOP捕捉是无效的
}

信号发送函数

#include 
#include 
/*
此函数给pid进程发送sig的信号
成功返回0,失败-1
*/
int kill (pid_t pid, int sig);

带消息的信号

#include 
/*
高级的消息注册函数
参数依次是消息类型、消息内容、消息备份,不需要为NULL即可
消息内容是一个指向struct sigaction的指针,该结构在头文件中被声明,因此需要一个结构体变量
该结构体需要存储的是接收消息后的handler函数的指针,以及flag等,一般做下述赋值后,将这个结构体的指针给注册函数
*/
struct sigaction act;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;

int sigaction (int signum, const struct sigaction* act, struct sigaction* oldact);
//其中传入结构的handler函数格式被定义,应是如下格式
void handler (int signum, struct siginfo_t *info, void *context){
     
	//此函数依次被系统传入参数为消息类型,指向消息内容的结构体,以及是否存在内容(context非NULL为有内容)
    if(context){
     }				//消息不为空时
    info->si_int;				//接收的int消息
    info->si_value.sival_int;	//接收的int消息
    info->si_pid;				//发送者
    //等
} 
#include 
/*
高级的消息发送函数
参数依次为进程id,消息类型,以及联合体变量
联合体需要自行申明
*/
union semun {
     
       int              val;    /* Value for SETVAL */
       struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
       unsigned short  *array;  /* Array for GETALL, SETALL */
       struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                   (Linux-specific) */
}

int sigqueue (pid_t pid, int signum, const union sigval value);
//如value.sival_int = 100;	发送num

信号量(semaphore)

概述

首先它和信号没有什么关系

用途:它是一种计数器,常常被用做一个锁机制,对于某些进程的共享资源,同一时间只能一个进程进行操作临界资源,以避免数据出错混乱

原理:

设信号量值为1
当一个进程1运行时,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这时信号量值为0。系统中规定当信号量值为0时,必须等待,直到信号量值不为零才能继续操作。
这时如果进程2想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,也就阻塞。这样就达到了进程1排他访问。
当进程1运行结束后,释放资源,进行V操作。资源数重新加1,这时信号量的值变为1.
这时进程2发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0,此时进程2占有资源,排他访问资源。

信号量集:信号量的集合

p操作:拿锁

v操作:放回锁

创建与控制信号量

#include 
#include 
#include 
/*
创建或访问一个新的信号量集合t
函数返回信号量集id,需要参数key,集合信号量的数量,以及权限(IPC_CREAT|0666)
*/
int semget (key_t key, int num_sems, int sem_flags);
/*
信号量控制函数
操作semid的第sem_num个信号量(0开始计数),指令为cmd(如SETVAL来初始化,IPC_RMID来移除信号锁)
*/
int semctl (int semid, int sem_num, int cmd, ...);
//根据cmd,最后参数可能需要一个联合体,联合体因该是如下形式
union semun {
     
       int              val;
       struct semid_ds *buf;
       unsigned short  *array;
       struct seminfo  *__buf;
};
union semun initsem;
initsem.val = 1;

获取和释放锁

#include 
#include 
#include 

/*对semid信号量集合numops个信号量进行操作,需要一个操作信息的结构体数组,以及数组中元素的个数*/

/*
p操作
*/
int semop (int semid, struct sembuf semoparray[], size_t numops);
//struct sembuf在头文件中被定义
struct sembuf {
     
    ushort sem_num;	/*label of semaphore*/
    short sem_op;	/*operation of semaphore >0 <0 or =0*/
    short sem_flg;	/*flag of semaphore*/
    							/*defined in linux/sem.h*/
}
//如下面是减少信号量的例子
semop (semid, &set, 1);
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1;	//为负数减去一个值,当为0进程被挂起进入休眠
set.sem_flg = SEM_UNDO;

/*
v操作
*/
int semop (int semid, struct sembuf semoparray[], size_t numops);
//如下面是增加信号量的例子
semop (semid, &set, 1);
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1;		//为正数增加一个值,当为0进程被挂起进入休眠
set.sem_flg = SEM_UNDO;

使用方法

int main(int argc, char const *argv[]) {
     
	int semid = semget (ftok(".",2), 1, IPC_CREAT|0666);	//建立一个具有一个信号量的信号量集合
	union semun {
     
       int              val;
       struct semid_ds *buf;
       unsigned short  *array;
       struct seminfo  *__buf;
   };
	union semun initsem;
	initsem.val = 0;
	semctl (semid, 0, SETVAL, initsem);						//初始化信号量,使信号量为0
	pid_t pid = fork();										//当然,默认是1
	if (pid >0){
     
		struct sembuf set1;
		set1.sem_num = 0;
		set1.sem_op = -1;
		set1.sem_flg = SEM_UNDO;
		semop (semid, &set1, 1);			//在信号量上无法减去了一个值,该进程挂起,等待子进程v操作
		printf("this is father print\n");		//子进程v操作后,父进程信号量可以进行减一,开始执行
		struct sembuf set2;
		set2.sem_num = 0;
		set2.sem_op = 1;
		set2.sem_flg = SEM_UNDO;
		semop (semid, &set2, 1);						//在信号量上增加了一个值,信号量为1
		semctl (semid, 0, IPC_RMID);					//移除信号量
	}
	else if (pid == 0){
     
		printf("this is child print\n");				//父进程被挂起时,子进程前半部分不受影响的执行
		struct sembuf set2;	
		set2.sem_num = 0;
		set2.sem_op = 1;
		set2.sem_flg = SEM_UNDO;
		semop (semid, &set2, 1);						//在信号量上增加了一个值,现在父进程可以执行了
	}
	return 0;
}

网络编程

多设备之间进程的通信,依赖于网络,需要借助地址:IP地址、端口号和协议

通信协议

Socket(套接字)通信协议:

​ TCP:面向链接:建立在链接基础上,然后进行通信,较为可靠的,仅支持一对一

​ UDP:面向报文:只管发送,而不管是否接收,尽最大努力交付,不保证可靠性,支持一对一或一对多

端口号:

IP地址的主机可以提供许多服务,基于一个IP,所以通过IP地址+端口号来实现

字节序

字节序是指多字节在计算机内存中存储或者网络传输时个字节的存储顺序

Litle endian(小端字节数):将低序字节存储在起始位置

Big endian(大端字节数):将低序字节存储在起始位置

网络字节序 = 大端字节序

API

#include 
#include 

/*
创建socket
domain指明协议如AF_INET(IPv4),type指明类型如SOCK_STREAM(TCP)、SOCK_DGRAM(UDP),protocol一般设为0
返回socket描述符,失败返回-1
*/
int socket (int domain, int type, int protocol);
//设置套接字后,内核将会进行相关系统调用交互

/*
为sockfd绑定IP和端口,客户端不需要这个函数(可以直接用connect链接服务器)
sockfd为描述符,addr是包含IP和端口号的结构体指针,addrlen为结构体的大小
成功返回0,失败返回-1,并设置errno
*/
int bind (int sockfd, const struct sockaddr* addr, socklen_t addrlen);
//结构体是struct sockaddr,但是不方便设置,所以一般采用下面的结构,在linux/in.h中被声明,需要强制转换
#include 
struct sockaddr_in {
     
    sa_family_t sin_family;		//协议 AF_INET
    in_port_t sin_port;			//端口号 5000~9000
    	//此处借助函数转化为网络字节序的值
    	uint16_t htons(uint16_t host16bitvalue);
    struct in_addr sin_addr;	//ip地址结构体
    	//结构体在头文件中被指定,只有一个s_addr网络字节整数成员
    	//获取sin_addr可用inet_aton函数
    		#include 
    		#include 
    		/*地址转换		将字符串形式的IP转化为网络能识别的结构体指针格式*/
			int inet_aton (const char* straddr, struct in_addr* sin_addr);
    unsigned char sin_zero[8];	//填充,可忽略
}
//定义出结构体,对成员进行赋值前,可先清空信息
/*清空addr,使用前一般先调用*/
#include
memset(struct sockaddr_in* addr, 0, sizeof(struct sockaddr_in));

/*
设置服务端监听,客户端不需要这个函数
sockfd监听backlog个连接,即当多个客户端请求同时到来时,服务器并不是同时处理,而是将不能处理的客户端链接放入等待序列中,而backlog就是队列的长度,超过长度将拒绝客户端,此时客户端将报错
成功运行时,返回0,失败为-1并设置errno
*/
int listen (int sockfd, int backlog);

/*
服务器接收一个请求,客户端不需要这个函数
本函数从等待连接队列中抽取第一个连接,没有链接时将会阻塞,创建一个新的套接字描述符用于获取客户端信息
成功链接到客户端后,客户端信息将保存在addr所指向的结构中,结构就是和客户端一样绑定的信息(IP地址,端口和协议等)
失败返回-1,成功返回描述符
*/
int accept (int sockfd, struct sockaddr* addr, socklen_t* addrlen);
//长度以指针形式传入
/*地址转换		将网络格式的IP转化为字符串*/
#include 
#include 
char* inet_ntoa (struct in_addr sin_addr);

/*
客户端链接目标服务器,客户端使用
执行后立刻返回,无法链接或服务器监听数量达到最大,将以-1返回
失败返回-1,成功返回0
*/
int connect (int sockfd, const struct sockaddr* addr, socklen_t addrlen);
//参数与bind类似,但addrlen以整形变量给出

/*
收发消息
flag一般设置为0,返回接收发送的字节数
s为指定发送端套接字描述符,读取信息将保存到msg所指向的内存空间
*/
ssize_t read (int s, const void* msg, size_t len, int flags);
ssize_t write (int s, const void* msg, size_t len, int flags);
ssize_t send (int s, const void* msg, size_t len, int flags);
ssize_t recv (int s, const void* msg, size_t len, int flags);

/*
关闭socket链接
s为套接字描述符
关闭套接字,关闭后将不能继续用此套接字描述符来读写
*/
int close (int s);

Socket服务器

int sockfd;
sockfd = socket (AF_INET, SOCK_STREAM, 0);		/*建立套接字*/
struct sockaddr_in s_addr;
memset(&s_addr, 0, sizeof(struct sockaddr_in));//清空
s_addr.sin_family = AF_INET;//设置协议
s_addr.sin_port = htons(9000);//设置端口
inet_aton ("192.168.8.108", &s_addr.sin_addr);//设置ip
bind (sockfd, (struct sockaddr*)&s_addr, sizeof(struct sockaddr_in));	/*设置服务器地址并绑定*/
listen (sockfd, 10);							/*设置监听队列*/
struct sockaddr_in c_addr;
memset(&c_addr, 0, sizeof(struct sockaddr_in));
int size = sizeof(struct sockaddr_in);
int sock_fd = accept (sockfd, (struct sockaddr*)&c_addr, &size);	/*接收链接*/
puts(inet_ntoa (c_addr.sin_addr));//打印客户端信息
char* msg = (char*)malloc(128);
write (sock_fd,"hello\n", 7);					/*读写数据*/
read (sock_fd, msg, 128);
printf("%s\n", msg);
getchar();
close(clnt_fd);					/*关闭套接字*/
return 0;

Socket客户端

int clnt_fd = socket (AF_INET, SOCK_STREAM, 0);			/*建立套接字*/
struct sockaddr_in c_addr;
memset(&c_addr, 0, sizeof(struct sockaddr_in));
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(9000);
inet_aton ("192.168.8.108", &c_addr.sin_addr);//配置服务器地址
int a =connect (clnt_fd, (struct sockaddr*)&c_addr, sizeof(struct sockaddr_in));	/*链接服务器*/
if (a == -1)
{
     
    perror("");
}
char* msg = (char*)malloc(128);
read (clnt_fd, msg, 128);			/*读写数据*/
printf("%s\n", msg);
write (clnt_fd,"hi\n", 4);
getchar();
close(clnt_fd);					/*关闭套接字*/
return 0;

makefile

target:dependencies
	command

test:test.c
	gcc test.c -o test

#当前文件夹下执行下面的命令
make
main:main.c tool.c
	gcc main.c tool.o -o main

tool.o:tool.c
	gcc -c tool.c

clean:
	rm *.o

#清除文件
make clean
main:main.c foo.o bar.o
	gcc main.c foo.o bar.o -o main
	
foo.o:foo.c
	gcc -c foo.c
	
bar.o:bar.c
	gcc -c bar.c
	
clean:
	rm *.o

CC = gcc

main:main.c foo.o bar.o
	$(CC) main.c foo.o bar.o -o main
	
foo.o:foo.c
	$(CC) -c foo.c
	
bar.o:bar.c
	$(CC) -c bar.c
	
clean:
	rm *.o

CC = gcc

all:main_max main_min

main:main_max.c foo.o bar.o
	$(CC) main_max.c foo.o bar.o -o main_max
	
main:main_min.c foo.o bar.o
	$(CC) main_min.c foo.o bar.o -o main_min
	
foo.o:foo.c foo.h
	$(CC) -c foo.c
	
bar.o:bar.c bar.h
	$(CC) -c bar.c
	
clean:
	rm *.o

Tools

Typora

你可能感兴趣的:(linux)