liunx系统编程

本文详细讲解了多道程序设计在多用户环境中的应用,进程的概念、内存隔离与管理,包括虚拟内存、文件I/O操作,以及进程和线程的区别、创建与控制。此外,涵盖了进程间通信的各种方法,如信号、管道、FIFO、共享内存等,并介绍了线程同步与通信机制。
摘要由CSDN通过智能技术生成

计算机系统概论

 

多到分时系统

  1. 利用多道程序设计处理多个交互工作
  2. 多个用户分享处理器时间
  3. 多个用户同时通过终端访问系统

进程

一个正在执行的程序,处于活跃状态,可以有多个线程但至少有一个线程,是一个完整的个体,可以分配给处理器并由处理器执行,是一个单一顺序的执行线索-单线程。

内存管理

  1. 进程隔离
  2. 自动分配和管理
           动态地分配,分配对程序员是透明的
  3. 支持模块化的程序设计
           能够定义程序模块,并且动态地创建、销毁模块,改变模块大小
  4. 保护和访问控制
  5. 长期存储

虚拟内存

  1. 允许程序员从逻辑的观点来进行访问存储器
  2. 满足由多个作业同时驻留在内存中的要求
  3. 当一个进程被写到辅助存储器中并且后继进程被读入时,在连续的进程执行之间不会脱节

基本文件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_WRONLYO_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

文件指针移动到文件结尾

  1. long feof(FILE* stream);

当文件内部位置指针指向文件末尾时,并未立即置位 FILE 结构中的文件结束标记,只有再执行一次读文件操作,才会置位结束标志,此后调用 feof 才会返回为真请读者注意下这句话:“只有再执行一次读文件操作,才会置位结束标志”

  1. long ftell(FILE *stream);

(当以追加的方式打开文件,在发生任何写入操作前文件指针移动到文件的末尾),如果以追加的方式打开文件且没有发生任何I/O操作,则文件指针在文件的开头,如果在未进行写操作,则指针又会回到末尾!!!

进程和线程

进程在linux中表示的数据结构?                   //task_struct

进程的执行模式?                                         //用户模式、内核模式

进程

进程具有独立的权限与职责。如果系统中某个进程崩溃,它不会影响到其余的进程

每个进程运行在其各自的虚拟地址空间中,进程之间可以通过由内核控制的机制相互通讯

ps指令

通常可以查看到:进程的ID、进程的用户ID、进程状态和进程的Command

ps -aux

  1. ps输出信息
  2. USER 进程的属主;
  3. PID 进程的ID;
  4. PPID 父进程;
  5. %CPU 进程占用的CPU百分比;
  6. %MEM 占用内存的百分比;
  7. NI 进程的NICE值,数值大,表示较少占用CPU时间;
  8. VSZ 进程虚拟大小;
  9. RSS 驻留中页的数量;
  10. TTY 终端ID
  11. WCHAN 正在等待的进程资源;
  12. START 启动进程的时间;
  13. TIME 进程消耗CPU的时间;
  14. COMMAND 命令的名称和参数;

STAT 进程状态

  1. D Uninterruptible sleep (usually IO)
  2. R 正在运行可中在队列中可过行的;
  3. S 处于休眠状态;
  4. T 停止或被追踪;
  5. Z 僵尸进程;
  6. < 优先级高的进程
  7. N 优先级较低的进程
  8. L 有些页被锁进内存;
  9. s 进程的领导者(在它之下有子进程);
  10. l is multi-threaded (using CLONE_THREAD, like NPTL
  11. pthreads do)
  12. + 位于后台的进程组;

进程状态变化关系图

进程控制

fork()

创建子进程

exec()

创建子进程后,引用其他程序

wait()

等待子进程退出,防止孤儿进程产生

及时释放子进程占用的系统资源

exit()

终止进程

获得进程有关的ID

#include <unistd.h>

#include <sys/types.h>

  1. uid_t getuid(void):获得进程的用户标识号。
  2. gid_t getgid(void):获得进程的用户所属的用户组ID。
  3. pid_t getpid(void)要获得当前进程的ID。
  4. pid_t getppid(void)获得当前进程的父进程的ID。
  5. pid_t getpgrp(void)获得当前进程所在的进程组的ID。
  6. 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()            //删除全部代码,鉴于用子进程运行

  1. int execl(const char *path, const char *arg, ...);
  2. int execlp(const char *file, const char *arg, ...);
  3. int execle(const char *path, const char *arg , ..., char *const envp[]);
  4. int execv(const char *path, char *const argv[]);
  5. int execvp(const char *file, char *const argv[]);
  6. int execve(const char *filename, char *const argv [],
  7. char *const envp[]);-系统调用

(带有字母“p” 的函数,第一个参数可以是相对路径或程序名,如果无法立即找到要执行的程序,那么就在环境变量PATH指定的路径中搜索。)

以上7个最后一个参数必须是NULL。 与v互斥

  1. execl("/bin/ls", "ls", "-l", NULL);
  2. 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
us)

如果子进程由于得到的信号(signal)没有被捕捉而导致退出时,该宏的值为真。

WIFSTOPPED(statu
s)

如果子进程没有终止,但停止了并可以重新执行时,该宏返回真。这种情况仅出现在waitpid调用中使用了WUNTRACED选项。

WEXITSTATUS(stat
us)

如果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_RR

SCHED_OTHER、 SCHED_FIFO、SCHED_RR

通信机制

信号量、信号、互锁、条件变量、读写锁

无名管道、有名管道、信号、消息队列、信号量、共享内存

线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一物理内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。

线程资源

线程自己基本上不拥有系统资源,只拥有少量在运行中必不可少的资源(如程序计数器、一组寄存器、栈、线程信号掩码、局部线程变量和线程私有数据),但是它可与同属一个进程的其他线程共享进程所拥有的全部资源(同一地址空间、通用的信号处理机制、数据与I/O)。

进程在使用时占用了大量的内存空间,特别是进行进程间通信时一定要借助操作系统提供的通信机制,这使得进程有自身的弱点,而线程占用资源少,使用灵活,很多应用程序中都大量使用线程,而较少的使用多进程,但是,线程不能脱离进程而存在,另外,线程的层次关系,执行顺序并不明显。

进线程应用的对比

创建线程

#include <pthread.h>

pthread_t  pth;

  1. 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可以使结构体,将更多的数据打包传输

  1. int pthread_join(pthread_t thread,void, **value_ptr);       //等待函数回收是二级指针

等待线程结束函数

  1. void pthread_exit(void *value_ptr);

例:p = (struct student*)malloc(sizeof(struct student));

pthread_exit(p);

  1. 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;

  1. 初始化线程属性

int pthread_attr_init(pthread_attr_t *attr);

  1. 销毁线程属性所占用的资源

int pthread_attr_destroy(pthread_attr_t *attr); 成功: 0;失败:错误号

  1. 线程分离状态的函数:设置线程属性,分离or非分离

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

  1. 获取程属性,分离or非分离

int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

  1. 参数: 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>

  1. 定义互斥锁:  

pthread_mutex_t mutex;

  1. 初始化互斥锁:

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步在创建线程之前完成!

  1. 加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

例:pthread_mutex_lock(mutex);

  1. 解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

  1. 销毁互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

死锁

死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁解决办法:

使用函数pthread_mutex_trylock(),它是函数pthread_mutex_lock()的非阻塞函数 :

int pthread_mutex_trylock(pthread_mutex_t *mutex);

自旋锁

自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

  1. int pthread_spin_destroy(pthread_spinlock_t *);
  2. int pthread_spin_init(pthread_spinlock_t *, int);
  3. int pthread_spin_lock(pthread_spinlock_t *);
  4. int pthread_spin_trylock(pthread_spinlock_t *);
  5. int pthread_spin_unlock(pthread_spinlock_t *)

读写锁

与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享

读写锁状态:

一把读写锁具备三种状态:

1. 读模式下加锁状态 (读锁)

2. 写模式下加锁状态 (写锁)

3. 不加锁状态

读写锁特性:

1.读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。

2.读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。

3.读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高

读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。

读写锁非常适合于对数据结构读的次数远大于写的情况。

  1. thread_rwlock_init函数
  2. pthread_rwlock_destroy函数    //关闭锁,也会是拿掉锁
  3. pthread_rwlock_rdlock函数
  4. pthread_rwlock_wrlock函数
  5. pthread_rwlock_unlock函数
  6. pthread_rwlock_tryrdlock函数
  7. pthread_rwlock_trywrlock函数                        //同上,参数相同

条件变量

条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。(就像一个人往仓库放货,多个人来仓库拿货,并且仓库只能被一个人使用,但是可以防止没货时,多人无效抢货,使用会有信号提醒。);

       pthread_mutex_t count_mutex;

pthread_cond_t count_threshold_cv;        //为全局变量

  1. 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;

  1. pthread_cond_destroy(pthread_cond_t *cond);函数
  2. pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

例:pthread_cond_wait(&count_threshold_cv,&count_mutex);

  1. pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);函数
  2. pthread_cond_signal(pthread_cond_t *cond);函数        

例:pthread_cond_signal(&count_threshold_cv);
                                          //唤醒至少一个阻塞在条件变量上的线程

  1. pthread_cond_broadcast(pthread_cond_t *cond);函数

相较于mutex而言,条件变量可以减少竞争。如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

进程间通信

总览

  1. 进程间通信的方法有?

信号、文件锁、管道、 FIFO、信号量、共享内存、消息队列

  1. 哪一种方法最有效,最快?

共享内存

  1. 哪些方法是system V IPC?

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

  1. 必须要求是亲属进程间才能通信的方法是?

管道

信号

  1. 硬件来源,比如我们按下了键盘或者其它硬件故障;
  2. 软件来源,最常用发送信号的系统函数是kill(), raise(), alarm()和setitimer()等函数,软件来源还包括一些非法运算等操作。

进程可以通过三种方式来响应和处理一个信号

信号处理办法

  1. 忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号不能被忽略, SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。
  2. 捕捉信号。通知内核在某种信号发生时调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理,这需要安装此信号。例如捕捉到SIGCHLD信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用waitpid()以取得该子进程的进程PID以及它的终止状态和资源。
  3. 执行系统默认操作。 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的状态)、转而执行中断服务程序,执行完成后再恢复到被中断的位置。当然,对于可抢占式内核,在中断返回时还将引发新的调度。

信号的发送

  1. 除了内核和超级用户,并不是每个进程都可以向其他的进程发送信号。
  2. 一般的进程只能向具有相同uid和gid的进程发送信号,或向相同进程组中的其他进程发送信号。
  3. 常用的发送信号的函数有kill()、 raise ()、 alarm()、setitimer()、 abort() 等。

kill()函数用来指定进程发送一个信号。

此函数的第一个参数为要传递信号的进程号(PD),第2个参数即发送的信号值。pid可以取以下几种值:

  1. pid>0:将信号发送给进程的PID值为pid的进程。v
  2. pid-0:将信号发送给和当前进程在同一进程组的所有进程。
  3. pid=-1:将信号发送给系统内的所有进程。,
  4. 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);

管道

管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,管道建立后,实际获得两个文件描述符:一个用于读取而另外一个用于写入。

  1. 管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道。
  2. 只能用于父子进程或者兄弟进程之间(具有亲缘关心的进程)。
  3. 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是单独构成一种文件系统,并且只存在于内存中。
  4. 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

管道的创建

系统调用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(管道)

  1. FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中
  2. 在文件系统中是一个有名字的管道
  3. 任何进程都可以打开
  4. 进程间无需关联

有名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:消息队列的操作

  1. IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中。
  2. IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm、msg_qbytes、 msg_ctime元素的值。这个值取自buf参数。
  3. 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:共享内存的操作

  1. IPC_STAT:读取一个共享内存的数据结构shmid_ds,并将其存储在buf指定的地址中。
  2. IPC_SET:设置消息队列的数据结构shmid_ds中各个元素的值。这个值取自buf参数。
  3. IPC_RMID:把共亨内存标记为可删除,当最后一个进程脱连此共享内存的时候,系统将删除该共享内存。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值