计算机系统概论
多到分时系统
- 利用多道程序设计处理多个交互工作
- 多个用户分享处理器时间
- 多个用户同时通过终端访问系统
进程
一个正在执行的程序,处于活跃状态,可以有多个线程但至少有一个线程,是一个完整的个体,可以分配给处理器并由处理器执行,是一个单一顺序的执行线索-单线程。
内存管理
- 进程隔离
- 自动分配和管理
动态地分配,分配对程序员是透明的 - 支持模块化的程序设计
能够定义程序模块,并且动态地创建、销毁模块,改变模块大小 - 保护和访问控制
- 长期存储
虚拟内存
- 允许程序员从逻辑的观点来进行访问存储器
- 满足由多个作业同时驻留在内存中的要求
- 当一个进程被写到辅助存储器中并且后继进程被读入时,在连续的进程执行之间不会脱节
基本文件I/O
off_t lseek(int fildes, off_t offset, int whence);
移动文件指针
SEEK_SET |
从文件头开始计算,文件指针移动到offset个字节位置。 |
SEEK_CUR |
从文件指针当前位置开始计算,向后移动offset个字节的位置。 |
SEEK_END |
文件指针移动到文件结尾 |
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
open()系统调用的标志
O_RDONLY |
以只读方式打开文件 |
O_WRONLY |
以只写方式打开文件 |
O_RDWR |
以读写方式打开文件 |
O_APPEND |
以追加模式打开文件,在每次写入操作指向之前,自动将文件指针定位到文件末尾。但在网络文件系统进行操作时却没有保证。 |
O_CREAT |
如果指定的文件不存在,则按照mode参数指定的文件权限来创建文件。 |
O_DIRECTORY |
假如参数pathname不是一个目录,那么open将失败。 |
O_EXCL |
如果使用了这个标志,则使用O_CREAT标志来打开一个文件时,如果文件已经存在,open将返回失败。但在网络文件系统进行操作时却没有保证。 |
O_NOCTTY |
打开一个终端特殊设备时,使用这个标志将阻止它成为进程的控制终端。 |
O_NOFOLLOW |
强制参数pathname所指的文件不能是符号链接。 |
O_NONBLOCK |
打开文件后,对这个文件描述符的所有的操作都以非阻塞方式进行。 |
O_NDELAY |
和O_NONBLOCK完全一样。 |
O_SYNC |
当把数据写入到这个文件描述符时,强制立即输出到物理设备。 |
O_TRUNC |
如果打开的文件是一个已经存在的普通文件,并且指定了可写标志(O_WRONLY、O_RDWR),那么在打开时就清除原文件的所有内容。但打开的文件是一个FIFO或者终端设备时, |
int access(const char *pathname, int mode);
文件权限参数
R_OK |
判断文件是否有读权限。 |
W_OK |
判断文件是否有写权限。 |
X_OK |
判断文件是否有可执行权限。 |
F_OK |
判断文件是否存在。 |
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
例:fd = open("./data.txt", O_CREAT|O_APPEND|O_WRONLY, 0777);
写例:ret = write(fd, a, sizeof(a)); //读略
open()函数指向图
访问文件的C库函数
FILE *fopen(const char *path, const char *mode);
例:FILE *fopen(“./data.txt”,”r”);
模式 |
模式说明 |
“r” |
以只读方式打开文件,文件打开时指针位于文件首。 |
“r+” |
以读和写方式打开文件,文件打开时指针位于文件首。 |
“w” |
以只写方式打开一个文件,如果文件已经存在,清空文件内容,如果文件不存,则创建一个新的文件。文件打开时指针位于文件首。 |
“w+” |
以读和写方式打开一个文件,如果文件已经存在,先清空文件内容,如果文件不存在,则创建一个新的文件。文件打开时指针位于文件首。 |
“a” |
以追加写的方式打开一个文件,如果文件不存在,则创建一个新文件,文件打开时指针位于文件尾,而且在每次写操作前,文件指针自动先移动到文件尾。 |
“a+” |
以追加写的方式打开一个文件,如果文件已经存在,先清空文件内容,如果文件不存在,则创建一个新文件,文件打开时指针位于文件尾,而且在每次写操作前,文件指针自动先移动到文件尾。 |
char *fgets(char *s, int size, FILE *stream);
char *fgets(char *s, int size, FILE *stream);
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); //二进制
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
文件随机存取
int fseek(FILE *stream, long offset, int whence); //和最上面那个用法一样
long ftell(FILE *stream);
int whence-->移动起始位置
SEEK_SET |
从文件头开始计算,文件指针移动到offset个字节位置。 |
SEEK_CUR |
从文件指针当前位置开始计算,向后移动offset个字节的位置。 |
SEEK_END |
文件指针移动到文件结尾 |
- long feof(FILE* stream);
当文件内部位置指针指向文件末尾时,并未立即置位 FILE 结构中的文件结束标记,只有再执行一次读文件操作,才会置位结束标志,此后调用 feof 才会返回为真请读者注意下这句话:“只有再执行一次读文件操作,才会置位结束标志”
- long ftell(FILE *stream);
(当以追加的方式打开文件,在发生任何写入操作前文件指针移动到文件的末尾),如果以追加的方式打开文件且没有发生任何I/O操作,则文件指针在文件的开头,如果在未进行写操作,则指针又会回到末尾!!!
进程和线程
进程在linux中表示的数据结构? //task_struct
进程的执行模式? //用户模式、内核模式
进程
进程具有独立的权限与职责。如果系统中某个进程崩溃,它不会影响到其余的进程。
每个进程运行在其各自的虚拟地址空间中,进程之间可以通过由内核控制的机制相互通讯
ps指令
通常可以查看到:进程的ID、进程的用户ID、进程状态和进程的Command
ps -aux
- ps输出信息
- USER 进程的属主;
- PID 进程的ID;
- PPID 父进程;
- %CPU 进程占用的CPU百分比;
- %MEM 占用内存的百分比;
- NI 进程的NICE值,数值大,表示较少占用CPU时间;
- VSZ 进程虚拟大小;
- RSS 驻留中页的数量;
- TTY 终端ID
- WCHAN 正在等待的进程资源;
- START 启动进程的时间;
- TIME 进程消耗CPU的时间;
- COMMAND 命令的名称和参数;
STAT 进程状态
- D Uninterruptible sleep (usually IO)
- R 正在运行可中在队列中可过行的;
- S 处于休眠状态;
- T 停止或被追踪;
- Z 僵尸进程;
- < 优先级高的进程
- N 优先级较低的进程
- L 有些页被锁进内存;
- s 进程的领导者(在它之下有子进程);
- l is multi-threaded (using CLONE_THREAD, like NPTL
- pthreads do)
- + 位于后台的进程组;
进程状态变化关系图
进程控制
fork()
创建子进程
exec()
创建子进程后,引用其他程序
wait()
等待子进程退出,防止孤儿进程产生
及时释放子进程占用的系统资源
exit()
终止进程
获得进程有关的ID
#include <unistd.h>
#include <sys/types.h>
- uid_t getuid(void):获得进程的用户标识号。
- gid_t getgid(void):获得进程的用户所属的用户组ID。
- pid_t getpid(void):要获得当前进程的ID。
- pid_t getppid(void):获得当前进程的父进程的ID。
- pid_t getpgrp(void):获得当前进程所在的进程组的ID。
- pid_t getpgid(pid_t pid):获得进程ID为pid的进程所在的进程组ID。
pid_t 进程id数据类型
派生进程
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回
值:
在父进程中, fork返回新创建子进程的进程PID。
在子进程中, fork返回0。
如果出现错误, fork返回-1。
system() exec()
system //不影响当前进程
例:system("ls -l; pwd; cat 1_fork.c");
exec() //删除全部代码,鉴于用子进程运行
- int execl(const char *path, const char *arg, ...);
- int execlp(const char *file, const char *arg, ...);
- int execle(const char *path, const char *arg , ..., char *const envp[]);
- int execv(const char *path, char *const argv[]);
- int execvp(const char *file, char *const argv[]);
- int execve(const char *filename, char *const argv [],
- char *const envp[]);-系统调用
(带有字母“p” 的函数,第一个参数可以是相对路径或程序名,如果无法立即找到要执行的程序,那么就在环境变量PATH指定的路径中搜索。)
以上7个最后一个参数必须是NULL。 与v互斥
- execl("/bin/ls", "ls", "-l", NULL);
- char *argv[] = {"ls", "-l", NULL};
execv("/bin/ls", argv)
exit(0) : 结束进程
等待进程
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
参数option 能够为0 或下面的OR 组合:
WNOHANG 假如没有任何已结束的子进程则马上返回,不予以等待。
WUNTRACED 假如子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
宏定义 |
含义 |
WIFEXITED(status) |
如果进程通过系统调用_exit或函数调用exit正常退出,该宏的值为真。 |
WIFSIGNALED(stat |
如果子进程由于得到的信号(signal)没有被捕捉而导致退出时,该宏的值为真。 |
WIFSTOPPED(statu |
如果子进程没有终止,但停止了并可以重新执行时,该宏返回真。这种情况仅出现在waitpid调用中使用了WUNTRACED选项。 |
WEXITSTATUS(stat |
如果WIFEXITED(status)返回真,该宏返回由子进程调用_exit(status)或exit(status)时设置的调用参数status值。 |
WTERMSIG(status) |
如果WIFSIGNALED(status)返回为真,该宏返回导致子进程退出的信号(signal)的值。 |
WSTOPSIG(status) |
如果WIFSTOPPED(status)返回真,该宏返回导致子进程停止的信号(signal)值。 |
例:if(WIFEXITED(status)) //如果为真,说明回收的子进程调用exit 或者 _exit 正常退出
wait();等待进程,假如没有任何已结束的子进程则马上返回,不予以等待
线程
应用功能 |
线程 |
进程 |
创建 |
pthread_create |
fork,vfork |
退出 |
pthread_exit |
exit |
等待 |
pthread_join |
wait、 waitpid |
取消/终止 |
pthread_cancel |
abort |
读取ID |
pthread_self() |
getpid() |
调度策略 |
SCHED_OTHER、 SCHED_FIFO、 |
SCHED_OTHER、 SCHED_FIFO、SCHED_RR |
通信机制 |
信号量、信号、互锁、条件变量、读写锁 |
无名管道、有名管道、信号、消息队列、信号量、共享内存 |
线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一物理内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。
线程资源
线程自己基本上不拥有系统资源,只拥有少量在运行中必不可少的资源(如程序计数器、一组寄存器、栈、线程信号掩码、局部线程变量和线程私有数据),但是它可与同属一个进程的其他线程共享进程所拥有的全部资源(同一地址空间、通用的信号处理机制、数据与I/O)。
进程在使用时占用了大量的内存空间,特别是进行进程间通信时一定要借助操作系统提供的通信机制,这使得进程有自身的弱点,而线程占用资源少,使用灵活,很多应用程序中都大量使用线程,而较少的使用多进程,但是,线程不能脱离进程而存在,另外,线程的层次关系,执行顺序并不明显。
进线程应用的对比
创建线程
#include <pthread.h>
pthread_t pth;
- int pthread_create(pthread_t *restrict thread, const pthread_attr_t, *restrict attr, void*(*start_routine)(void*), void *restrict arg); //第二个参数正常为空,第四个参数可以传参 (pthread_t 线程id数据类型)
例:pthread_create(&pth, NULL, pthread_task, &s) //s可以使结构体,将更多的数据打包传输
- int pthread_join(pthread_t thread,void, **value_ptr); //等待函数回收是二级指针
等待线程结束函数
- void pthread_exit(void *value_ptr);
例:p = (struct student*)malloc(sizeof(struct student));
pthread_exit(p);
- int pthread_join(pthread_t thread,void **value_ptr);
例:struct student *p = NULL; //承上
pthread_join(pth, (void**)&p);
void* pthread_task(void* argv)
线程退出函数
线程分离
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
int pthread_detach(pthread_t thread);
typedef struct
{
int etachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程的栈设置
void* stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
} pthread_attr_t;
- 初始化线程属性
int pthread_attr_init(pthread_attr_t *attr);
- 销毁线程属性所占用的资源
int pthread_attr_destroy(pthread_attr_t *attr); 成功: 0;失败:错误号
- 线程分离状态的函数:设置线程属性,分离or非分离
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
- 获取程属性,分离or非分离
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
- 参数: attr:已初始化的线程属性
detachstate: PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD _CREATE_JOINABLE(非分离线程)
例:pthread_attr_init(&attr) ;
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
pthread_create(&a,&attr,inc_count,(void*)&s); //pthread_t a;pthread_attr_t attr,s是传址
pthread_attr_destroy(&attr);
线程同步-互斥锁
#include <pthread.h>
- 定义互斥锁:
pthread_mutex_t mutex;
- 初始化互斥锁:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
可以用宏定义来定义:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init(&mutex,NULL);
ps : 第1 、2步在创建线程之前完成!
- 加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
例:pthread_mutex_lock(mutex);
- 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
死锁
死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁解决办法:
使用函数pthread_mutex_trylock(),它是函数pthread_mutex_lock()的非阻塞函数 :
int pthread_mutex_trylock(pthread_mutex_t *mutex);
自旋锁
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
- int pthread_spin_destroy(pthread_spinlock_t *);
- int pthread_spin_init(pthread_spinlock_t *, int);
- int pthread_spin_lock(pthread_spinlock_t *);
- int pthread_spin_trylock(pthread_spinlock_t *);
- int pthread_spin_unlock(pthread_spinlock_t *)
读写锁
与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。
读写锁状态:
一把读写锁具备三种状态:
1. 读模式下加锁状态 (读锁)
2. 写模式下加锁状态 (写锁)
3. 不加锁状态
读写锁特性:
1.读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。
2.读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
3.读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高
读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。
读写锁非常适合于对数据结构读的次数远大于写的情况。
- thread_rwlock_init函数
- pthread_rwlock_destroy函数 //关闭锁,也会是拿掉锁
- pthread_rwlock_rdlock函数
- pthread_rwlock_wrlock函数
- pthread_rwlock_unlock函数
- pthread_rwlock_tryrdlock函数
- pthread_rwlock_trywrlock函数 //同上,参数相同
条件变量
条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。(就像一个人往仓库放货,多个人来仓库拿货,并且仓库只能被一个人使用,但是可以防止没货时,多人无效抢货,使用会有信号提醒。);
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv; //为全局变量
- pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参2: attr表条件变量属性,通常为默认值,传NULL即可
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_init (&count_threshold_cv,NULL);
pthread_cond_t count_threshold_cv = PTHREAD_COND_INITIALIZER;
- pthread_cond_destroy(pthread_cond_t *cond);函数
- pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
例:pthread_cond_wait(&count_threshold_cv,&count_mutex);
- pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);函数
- pthread_cond_signal(pthread_cond_t *cond);函数
例:pthread_cond_signal(&count_threshold_cv);
//唤醒至少一个阻塞在条件变量上的线程
- pthread_cond_broadcast(pthread_cond_t *cond);函数
相较于mutex而言,条件变量可以减少竞争。如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。
进程间通信
总览
- 进程间通信的方法有?
信号、文件锁、管道、 FIFO、信号量、共享内存、消息队列
- 哪一种方法最有效,最快?
共享内存
- 哪些方法是system V IPC?
信号量、共享内存、消息队列
- 必须要求是亲属进程间才能通信的方法是?
管道
信号
- 硬件来源,比如我们按下了键盘或者其它硬件故障;
- 软件来源,最常用发送信号的系统函数是kill(), raise(), alarm()和setitimer()等函数,软件来源还包括一些非法运算等操作。
进程可以通过三种方式来响应和处理一个信号
信号处理办法
- 忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号不能被忽略, SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。
- 捕捉信号。通知内核在某种信号发生时调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理,这需要安装此信号。例如捕捉到SIGCHLD信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用waitpid()以取得该子进程的进程PID以及它的终止状态和资源。
- 执行系统默认操作。 Linux系统对任何一个信号都规定了一个默认的操作。
信号处理函数的安装
#include<signal.h>
void( *signal(int sig, void( *func)(int)))(int);
int sig->要安装的信号值。 *func->信号值处理函数
如果func不是函数指针,必须是下列两个宏:
SIG_IGN:忽略信号。
SIG_DEF:采用系统默认的方式处理信号,执行缺省操作。
返回值:返回先前的信号处理函数指针,如果有错误则返回-1。
信号的处理流程
(1)信号被某个进程产生,并设置此信号传递的对象(一般为对应进程的pid),然后传递给操作系统;
(2)操作系统根据接收进程的设置(是否阻塞)而选择性的发送给接收者,如果接收者阻塞该信号(且该信号是可以阻塞的),操作系统将暂时保留该信号,而不传递,直到该进程解除对此信号的阻塞(如果对应进程已经退出,则丢弃此信号);如果对应进程没有阻塞,操作系统将传递此信号;
(3)目的进程接收到此信号后,将根据当前进程对此信号设置的预处理方式,暂时终止当前代码的执行,保护上下文(主要包括临时寄存器数据、当前程序位置以及当前CPU的状态)、转而执行中断服务程序,执行完成后再恢复到被中断的位置。当然,对于可抢占式内核,在中断返回时还将引发新的调度。
信号的发送
- 除了内核和超级用户,并不是每个进程都可以向其他的进程发送信号。
- 一般的进程只能向具有相同uid和gid的进程发送信号,或向相同进程组中的其他进程发送信号。
- 常用的发送信号的函数有kill()、 raise ()、 alarm()、setitimer()、 abort() 等。
kill()函数用来指定进程发送一个信号。
此函数的第一个参数为要传递信号的进程号(PD),第2个参数即发送的信号值。pid可以取以下几种值:
- pid>0:将信号发送给进程的PID值为pid的进程。v
- pid-0:将信号发送给和当前进程在同一进程组的所有进程。
- pid=-1:将信号发送给系统内的所有进程。,
- pid<0:将信号发送给进程组号PGID为pid绝对值的所有进程。
如果成功完成返回值0,否则返回值-1,并设置erno 以指示错误。
raise()函数:
给进程本身发送一个信号
#include <signal.h>
int raise(int sig); //相当于kill(getpid(),sig);
返回值:成功为0;失败返回-1。
alarm()函数:
是一个简单定时器,专为SIGALRM信号设计
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
在指定的seconds秒之后,给进程本身发生一个SIGALRM信号
setitimer()
功能强大更强大的定时器函数,支持3种类型的定时器,但是从本质上,它是和alarm共享同一个进程内的定时器
#include <sys/time.h>
int setitimer(int which, const struct itimerval *value, struct itimerval*ovalue);
struct itimerval
{
struct timeval it_interval; /* 间隔时间 */
struct timeval it_value; /* 开始时间*/
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
例:signal(SIGALRM, hangle);
struct itimerval it;
it.it_value.tv_sec = 2;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 1;
it.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &it, NULL);
abort():
向进程发送SIGABORT信号,默认情况下进程会异常退出
fcntl()系统调用
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg); // long arg文件描述符
// cmd参数
F_GETLK:获取文件锁当前的状态。
F_SETLK:设置文件锁。
F_SETLKW:这是F_SETLK的阻塞版本,如果新加的锁被拒绝,那么进程被阻塞直到可以加锁。
int fcntl(int fd, int cmd, struct flock *lock); //*lock如下实例
例:fcntl(fd,F_SETLKW,&fk); //fk为struct flock fk;的结构体
struct flock {
...
short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */--->读写
short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock(set by F_GETLK and F_OFD_GETLK) */
...
};
l_type类型:
F_RDLCK:读锁
F_WRLCK:写锁
F_UNLCK:解锁
例:fk.l_whence = SEEK_SET;
fk.l_start = 0;
fk.l_len = 1024;
fk.l_type = F_RDLCK;
fcntl(fd,F_SETLKW,&fk);
管道
管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,管道建立后,实际获得两个文件描述符:一个用于读取而另外一个用于写入。
- 管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道。
- 只能用于父子进程或者兄弟进程之间(具有亲缘关心的进程)。
- 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是单独构成一种文件系统,并且只存在于内存中。
- 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
管道的创建
系统调用pipe()用于创建一个管道
int pipe(int filedes[2]); // int filedes[2],注意:是俩个
建立管道:
filedes[0]: 为pipe的读出端
filedes[1]: 为pipe的写入端
两个文件描述符数组。
管道的创建尽量在创建子进程前创建
1、管道两端都是正常的, 如果管道里没有数据, read函数是阻塞的,
如果写端断开或者出现异常, read函数解除阻塞,返回值为0
2、如果读端异常, 写端继续往管道中写数据,内核会发送SIGPIPE信号,
该信号的默认操作是程序退出, 所以写端一定要捕捉 SIGPIPE信号
例:signal(SIGPIPE, hangle);
void hangle(int sig){ }
FIFO(管道)
- FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中
- 在文件系统中是一个有名字的管道
- 任何进程都可以打开
- 进程间无需关联
有名FIFO的创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode); //文件名,权限
例:ret = mkfifo("./b.txt", 0777); //两个进程都要
if(ret<0)
{
if(errno != 17)
{
printf("errno = %d\n", errno);
perror("mkfifo");
return -1;
}
}
创建消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
函数功能: 创建 或者 获取消息队列的标识符
参数说明:
key : 键值, 一个键值和一个消息队列是一一对应的关系
msgflg: 设置标志, 一般使用 IPC_CREAT|0777 (mode_flags:类似于文件的权限)
返回值:成功返回消息队列的标识符 失败返回-1
例:msgid = msgget(0x100, IPC_CREAT|0777);
if(msgid < 0)
{
perror("msgget");
return -1;
}
发送和接收消息
msgsnd()系统调用用于向队列发送一条消息:
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, intmsgflg);
参数说明:
msqid: 消息队列标识符
msgp: 打包消息信息结构体的起始地址
msgsz: 消息大小
msgflg: 设置阻塞标志, 0 表示阻塞
返回值:成功返回0, 失败返回-1
例:struct msgbuf mb = {2, "xiaoqi"};
msgsnd(msgid, &mb, 6, 0);
msgrcv()系统调用用于从消息队列读取一条消息:
ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg);
参数说明:
msqid : 消息队列的标识符
msgp: 接收消息数据结构体的起始地址
msgtype: 消息类型
如果类型是0, 默认读取第一个消息
如果类型是非零值, 从第一个消息开始查找,读取指定类型的消息
msgflg: 设置阻塞标志, 0表示阻塞
例:ret = msgrcv(msgid, &mb, 1024, 5, 0);
消息队列的控制
通过msgctl()可以对消息队列进行控制或者一些属性的修改:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
cmd:消息队列的操作
- IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中。
- IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm、msg_qbytes、 msg_ctime元素的值。这个值取自buf参数。
- IPC_RMID:从系统内核中移走消息队列。
struct msqid_ds
{
struct ipc_perm msg_perm; /*所有者和权限*/
time_t msg_stime; /*最后一次向队列发送消息的时间*/
time_t msg_rtime; /*最后一次从队列接收消息的时间*/
time_t msg_ctime; /*队列最后一次改动的时问*/
unsigned long __msg_cbytes; /*当前队列所有消息的总长度*/
msgqnum_t msg_qnum; /*当前队列中的消息数量*/
msglen_t msg_qbytes; /*消息队列的最大消息总长度*/
pid_t msg_lspid; /*最一次给队列发送消息的进程PID*/
pid_t msg_lrpid; /*最后一次从队列接收消息的进程PID*/
}
共享内存
两个不同进程A、 B共享内存的基本原理是,同一块物理内存被映射到进程A、 B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然
创建和获取共享内存
系统调用shmget()用于创建共享内存或者获取一个已经存在的共享内存的标识符:
int shmget(key_t key, size_t size, int shmflg);
key:键值
1. 指定键值
2. IPC_PRIVATE
系统指定键值
size:共享内存大小
msgflg :共享内存标志
1.IPC_CREATE
如果内核中没有此队列,则创建它。
2.IPC_EXECL
当和IPC_CREAT一起使用时,如果队列已经存在,则返回错误。
3.mode_flags:类似于文件的权限 //例:IPC_CREAT|0777
例:int shmid;
shmid = shmget(0x200, 100, IPC_CREAT|0777);
if(shmid < 0)
{
perror("shmget");
return -1;
}
系统调用shmat()可以获取一个共享内存的地址,并将其连接到进程中:
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:
由shmget返回的共享内存标志
shmaddr: //一般使用NULL,由系统指定
映射该共享内存块的进程内存地址
如果为NULL, Linux将自动选择合适的地址
shmflg: //一般默认为0
SHM_RND
SHM_RDONLY
例:char * p = shmat(shmid, NULL, 0);
注:拷贝时应该使用strcpy或者memcpy
通过shmctl()可以对消息队列进行控制或者一些属性的修改:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
cmd:共享内存的操作
- IPC_STAT:读取一个共享内存的数据结构shmid_ds,并将其存储在buf指定的地址中。
- IPC_SET:设置消息队列的数据结构shmid_ds中各个元素的值。这个值取自buf参数。
- IPC_RMID:把共亨内存标记为可删除,当最后一个进程脱连此共享内存的时候,系统将删除该共享内存。