linux应用编程基础学习笔记


 ********************************************************
            --文件I/O--

文件:
文本文件:存储量大,速度慢,便于字符操作
二进制文件:存储量小,速度快,便于存放中间结果

普通文件:
设备文件:


---C标准函数---:
Buffered I/O,高级文件系统,在用户空间开辟缓冲区,流操作(stream)
#include<stdio.h>

typedef struct
{
 int _fd;      //文件号
 int _cleft;   //缓冲区剩下的字符数
 int _mode;    //文件操作方式
 char *_next;  //文件当前读写位置
 char *_buff;  //文件缓冲区位置
}FILE;

系统自动打开关闭三个标准文件:
标准输入  ---键盘    stdin    0
标准输出  ---显示器        stdout   1
标准错误输出 ---显示器     stderr   2

缓存设置
int setbuf(FILE* fp,int buftype)
int buftype: _IOFBF //满缓存
             _IOLBF //行缓存(换行清空缓存)
      _IONBF //无缓存

清楚缓存
getchar()
fflush(stdin|stdout|stderr|FILE*)


操作方式:打开文件(返回File指针)--> 文件读写(操作FILE指针) --> 关闭文件(操作FILE指针)

打开文件
FILE *fopen(char * filename,char * mode)
char * mode:打开方式
   字符文件:w r  w+  r+  wr  a
   二进制文件:wb rb r+b  w+b a+b
 返回值:NULL 失败


关闭文件(先把缓存中字符写到文件)
void fclose(FILE *f)

文件读读(以块方式读)
size_t  fread(void *buf,size_t size,int num,FILE *fp);
size_t size:读取块的单元大小
int num:块数
返回:返回 成功读取的块数

文件写(以块方式写)
size_t  fwrite(void *buf,size_t size,int num,FILE *fp);
size_t size:块的单元大小
int num:块数
返回:返回  成功读取的块数

字符读写
int fgetc(FILE* fp)从文件读取单个字符
int  fputc(int c,FILE* fp)单个字符写入文件
char * fgets(char* buf,size_t size,FILE* fp)从文件读取多个字符(遇到\n结束)
size_t size:sizeof(buf)
fputs(char * c,FILE* fp)

格式化输入输出
fprintf(FILE* fp,"%d%s%c",int a,char* s,char c);
fscanf(FILE* fp,"%d%s%c",int a,char* s,char c)
  
 
fseek(FILE * fp,off_t off ,int whence)
int ftell(FILE * fp)
fgetpos(FILE * fp,post_t* pos)
fsetpos(FILE * fp,post_t* pos)


---系统函数---
Unbuffered I/O,低级文件系统

操作方式:打开文件(返回文件描述符)--> 文件读写(操作文件描述符) --> 关闭文件(操作文件描述符)
系统自动打开关闭三个标准文件:
标准输入  ---键盘    STDIN_FILENO     0
标准输出  ---显示器        STDOUT_FILENO    1
标准错误输出 ---显示器     STDERR_FILENO    2

创建文件
int creat(char* pathname,mode_t mode)
返回 -1:失败
mode_t  mode:S_IRUSR S_IWUSR  S_IXUSR
               S_IRGRP S_IWGRP  S_IXGRP
        S_IROTH S_IROTH  S_IROTH

打开:open
int open(char * pathname,int flags)
int open(char * pathname,int flags,mode_t mode)

返回值是整数,表示用户进程所开启的文件的编号(0-255),             失败  -1;
int flags:用户对文件的操作权限
 O_RDONLY   只读
 O_WRONLY   只写
 O_RDWR     读写
 以上三个必须要选择其中一个
 O_APPEND  写在已有内容后面
 O_TRUNC   清楚已有的内容,写入新的内容
 O_CREAT   创建文件
 O_EXCL     与O_CREAT一起使用,判断文件是否已经存在

 可以组合:O_RDONLY|O_APPEND  O_WRONLY |O_TRUNC


 mode_t mode:  S_IRUSR S_IWUSR  S_IXUSR
               S_IRGRP S_IWGRP  S_IXGRP
        S_IROTH S_IROTH  S_IROTH

只有在使用 O_CREAT 才需要指定该参数值
使用O_CREAT 创建文件是以只写方法打开

关闭:close
返回 -1  失败
文件读
size_t  read(int fd,void* buf,size_t count)

返回值:  0:读取完
         -1:错误
  >0:成功读取的字节数
int fd:文件标示
void* buf:指向保存读取文件内容内存
size_t count:指定准备读取的字节数,返回值<=count


写文件
size_t write(int fd,void *buf,size_t count)
返回值: -1:失败
        >0:成功写入字节数
int fd:文件标示
void* buf:指向存储内容的地址空间
size_t count:数组中实际字符数

od -c filename  //查看文件内部情况

文件空洞

不改变文件的偏移量,读或写文件
pread pwrite
size_t  pread(int fd,void *buff,size_t size,off_t off)
off_t off:设置文件的偏移量
读完之后,文件的偏移量没有变化
size_t pwrite(int fd,void * buff,size_t size,oof_t off)


文件操作

文件的偏移量:相对于文件开头的一段偏移距离,是一个非负整数
移动文件偏移量函数
off_t lseek(int fd,off_t off,int whence)
返回值:是相对于文件开头的距离
int fd:文件操作符
off_t off:移动偏移量,根据相对点不同off可以使负数
int whence:移动相对点
    SEEK_SET 0 文件开头
    SEEK_CUR 1 文件当前位置
    SEEK_END 2 文件末尾
lseek(fd,0,2);//文件大小

umask
umask 设置禁止用户对文件默认模式
umask 0022//对组中其他用户和其他用户禁止有写的权限

int umask(mode_t mode)
禁止 mode 模式
返回-1  失败

检验文件是否具有指定模式
int access(char* filename,mode_t mode);
mode_t mode: R_OK 是否读
             W_OK 是否写
      X_OK 是否执行
      F_OK 是否存在
返回值: 0: 验证成功
        -1 :验证失败

 


文件状态
#include<stat.h>

int stat(char* pathname,struct stat * s);
char* pathname:测试文件的名或路径
struct stat * s:指向保存文件状状态信息结构变量
返回值:  -1 :失败

#include<bits/stat.h>
struct stat
{
   mode_t  st_mode;//文件模式(文件类型 文件rwx)
   ino_t   st_inol;//文件节点(ls -i)
   dev_t   st_dev;//设备号(设备文件)
   dev_t   st_rdev//特殊设备编号
   nlink_t st_nlink;//链接数
   uid_t   st_uid;//文件所有者ID
   gid_t   st_gid;//文件所有者组ID
   off_t   st_size;//文件字节数(字符文件)
   time_t  st_atime;//最后一次访问时间
   time_t  st_mtime;//最后一次修改时间
   time_t  st_ctime;//最后一次文件状态改变时间
   blksize_t st_blksize;//块大小
   blkcnt_t  st_blkcnt;//块数量
}

获取已经打开的文件状态
int  fstat(int fd,struct stat* s)

获取文件状态信息,如果文件类型是链接(L),获取的状态是符号链接的文件,但是stat获取的是符号链接的源文件状态信息
int lstat(char * pathname,struct stat* s);


#include<sys/stat.h>
检测文件类型函数
 S_ISDIR(st_mode) ;//是否是目录
 S_ISCHR(st_mode) ;//是否是字符文件
 S_ISBLK(st_mode);// 是否是设备文件
 S_ISREG(st_mode);// 是否是一般文件
 S_ISLNK(st_mode);//是否是链接文件
 S_ISFIFO(st_mode);//管道文件
 S_ISOCK(st_mode);//套接字文件


 获取用户名
 #include<pwd.h>
struct passwd
{
  char *pw_name;                /* Username.  */
  char *pw_passwd;              /* Password.  */
  __uid_t pw_uid;               /* User ID.  */
  __gid_t pw_gid;               /* Group ID.  */
  char *pw_gecos;               /* Real name.  */
  char *pw_dir;                 /* Home directory.  */
  char *pw_shell;               /* Shell program.  */
};
struct passwd *getpwuid (__uid_t __uid);


#include<grp.h>
/* The group structure.  */
struct group
  {
    char *gr_name;              /* Group name.  */
    char *gr_passwd;            /* Password.    */
    __gid_t gr_gid;             /* Group ID.    */
    char **gr_mem;              /* Member list. */
  };
struct group *getgrgid (__gid_t __gid);

 

获取当前目录
char* getcwd(char* buf,size_t size);
char* buf:存放当前的目录
size_t size:buf空间数,一般sizeof(buf)
返回值:返回当前目录
getcwd(NULL,0)


改变文件或目录模式
int chmod(char * filepath,mode_t mode)
改变指定文件或目录的模式
返回值: 0:成功
        -1:失败

int lchmod(char * filepath,mode_t mode)
改变指定文件或目录的模式,如果是符号链接(ln -s),改变的是符号链接文件,而非符号链接源文件
返回值: 0:成功
        -1:失败


改变工作目录
int chdir(char * pathfile);

int fchdir(int fd);
改变的是已经open文件的工作目录

删除文件
int  remove (char * pathfile)
只能删除空目录


---目录---

#include <dirent.h> 目录头文件
创建目录
int  mkdir(char *dirname,mode_t mode)

打开目录
DIR* opendir(char * dirname);
DIR* fopendir(int fd)
DIR:表示目录类型

读取目录
struct dirent * readdir(DIR* dir)
目录结构类型
struct dirent
{
   ino_t d_ino;//目录编号(文件节点)
   off_t d_off;//目录偏移量
   int   d_reclenl;//目录最近大小
   char  d_type;目录类型
   char  d_name[10];//目录名,打开目录的子目录名
}

stat(char *filename,struct stat*st)
lstat(char *filename,struct stat*st)
fstat(int fd,struct stat*st)


关闭
int closedir(DIR * dir)

删除目录
int remove(char * dirname)
删除空目录


---内存映射---

把存储磁盘上的空间当作内存使用,把磁盘所分配的空间地址存放到内存,根据文件表示符找到映射区域(映射文件)

内存映射使用情况:
1.对大文件操作
2.多进程之间的共享

内存映射使用函数:
#includee <sys/mman.h>

创建内存映射函数:
void * mmap(void * v, int size, int options,int flags, int fd, off_t off);
返回值:映射成功后的内存地址
参数:void * v  指定内存地址(一般为0,内核分配)
     int size  映射的空间大小
     int options  访问映射内存的权限(PROT_READ PROT_WRITE)
     int flags   是否共享给其它进程使用( MAP_SHARED  MAP_PRIVATE)
     int fd   磁盘上的文件标示符号
     off_t off 设置磁盘上文件的偏移量

释放内存映射空间
 munmap(void* , size_t len);  

内存映射读:把文件的内容当作内存中的数组元素操作(元素个数:文件的元素数,数组地址:mmap函数返回值)

内存映射写:把要写的内容写到内存中,再利用文件的异步操作,把写的内容写到文件

文件异步操作函数:
msync(void * addrs,size_t size ,int flags);
void * addrs:内存映射返回的地址
size_t size :更新或写入块大小
int flags: 异步文件方式(MS_ASYNC,MS_SYNC)

 

 

*************************************************************
               ---进程---

进程概念
进程是一个正在执行的程序,内核分配独立的资源给该进程使用

进程特点:
1.进程并发性
2.进程独立性(PCB)

PCB
进程控制块(task_struct结构体)存放进程所需要的数据资源代码,只有进程自己能使用PCB

组成:
1.代码
2.数据
3.堆栈(stack)
4.进程ID(PID:进程的唯一标示符,内核自动分配产生,PID可能多次被不同进程使用)
5.进程状态(运行 等待 睡眠)
6.当前工作目录
7.umask掩码
8.文件描述符表
9.用户ID,组ID
10.控制终端
……

linux系统两个特殊的进程:
1.pid=0; 内核进程,执行内核中程序(进程调度)
2.pid=1; 引导(初始化)进程,操作系统最先执行的进程,最后关闭进程,可以收留任何一个孤儿进程

进程分类:
1.内核进程
2.用户进程

僵尸进程
一个进程运行完,或则调用exit而终止后,会关闭文件描述符,释放用户内存空间,但是在内核进程列表中还保留它的PCB数据结构的一些退出信息,这时进程称为僵尸进程,会发送SIGCHLD信号,系统会检查谁是它的父进程:
1.如果有父进程,父进程接收到信号并调用wait/waitpid进行回收。
2.如果没有父进程,则init进程会进行回收。
3.如果有父进程,是一个循环不会结束,并且没有wait/waitpid进行回收,也没有忽略该信号,则该子进程就会永远是僵尸进程Zombie。

解决办法:
1.编程时用wait/waitpid。
2.fork两次
3.在SVR4张,调用signal或sigset将SIGCHLD配置为忽略
4.SVR4的sigaction,设置SA_NOCLDWAIT标志。

注意:僵尸进程不能用kill(kill就是终止进程的),僵尸进程以及终止,解决大量僵尸进程只能重启。


linux程序中如何创建新的进程

创建子进程
fork
进入内核,一次调用,两次返回。
pid_t :表示进程编号
pid_t  fork();
返回值: ret<0:创建子继承失败
        ret==0:创建子进程成功,子进程执行语句
   ret>0:创建子进程成功,返回的是子进程的PID,父进程执行语句。


fork创建的子进程特点:
1.子进程复制父进程的PCB,从fork函数返回之后开始执行
2.子进程与父进程执行的先后顺序是不确定
3.如果fork创建子进程的父进程先结束,其父进程会是init进程

获取当前进程的PID
pid_t  getpid()
获取当前进程的父进程PID
pid_t  getppid()


创建子进程
vfork
pid_t vfork();
返回值: pid>0:父进程执行的语句
        pid==0:子进程执行的语句
 pid<0:错误
vfork创建子进程特点:
1.子进程不会复制父进程的PCB
2.父进程创建子进程后,父进程堵塞,让子进程先运行
  

进程结束
1.程序运行完毕。
2.return;//函数返回,主函数返回则退出。
3.exit(退出状态值);//exit函数调用_exit函数,先处理缓存中的数据
4._exit(退出状态值)
5.void atexit(void(*fun)());//子进程正常退出执行注册函数

exit 与 _exit区别
exit函数调用_exit函数,先处理缓存中的数据
_exit不会处理缓存中的数据


在fork创建子进程中执行其它的程序
linux 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 execvpe(const char *file, char *const argv[],
                  char *const envp[]);
exec家族函数 只是用新的程序替换原来程序,会利用新进程的代码 数据 堆栈

返回值:-1:失败


等待进程结束
pid_t wait(int *status);
一般用在父进程等待子进程结束,把父进程挂起,直到只要有一个子进程结束,父进程唤起

返回值:结束子进程PID
int *status:获取结束子进程的结束状态值


waitpid 等待进程结束

 pid_t waitpid(pid_t pid,int* status,int option);
 pid_t pid:  pid>0:等待指定的子进程
             pid==0:等待同组的任意进程
      pid==-1:等待任意一个子进程(wait)
      pd<-1:等待与绝对值同组的任意进程

int option:  0/NULL :父进程挂起,等待子进程结束
             WNOHANG:立即结束(没有子进程结束)

返回值: 0:父进程非堵塞状态等待指定的进程结束,继续等
           waitpid(c2,NULL,WNOHANG);
        pid:结束的进程
 -1:错误
 
#include <assert.h>
错误检测,发生错误程序停止执行
assert(scalar expression);
scalar expression 为假,才能放弃程序执行

子进程正常退出执行注册函数
atexit
void atexit(void(*fun)());


---进程通信---

进程通行方式:
UNIX最初--
        信号:
             signal
       alarm
       raise
       kill
       pause
        管道:匿名管道    pipe
     (内核)   dup dup2
      有名管道   mkfifo
               (FIFO文件)
system V -- ipc:1.shm共享内存
      2.msg消息队列
      3.sem信号量
BSD          -- socket套接字

信号
信号是一种软中断,是进程进程之间通信的一种符号,由内核管理。是进行异步传输

kill  -l 输出linux所有信号
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT
17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU
25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH
29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN
35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4
39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6
59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX


常见:
SIGINT 终止进程
SIGTSTP 停止进程
SIGCONT     继续进程
SIGQUIT     终止进程并产生core文件(进程异常终止后把用户空间数据保存到磁盘,默认不允许产生,可以  用ulimit -c 1024 命令修改文件的大小)

SIGFPE      除以0
SIGSEGV     段错误

SIGALRM     闹钟
SIGPIPE     读端关闭的管道写数据


信号的产生:
(要么内核产生(硬件),要么用户程序产生再发送给内核,内核再发送个对应的进程)

1.用户在终端按下Ctrl-C等键时,终端驱动程序发送信号给前台程序
Ctrl-C: SIGINT 终止进程
Ctrl-Z: SIGTSTP 停止进程
Ctrl-\: SIGQUIT 终止进程并产生core文件

2.硬件异常,内核将异常的信号发送给进程
除以0:SIGFPE
段错误:SIGSEGV

3.进程调用kill()函数发送信号给另一个进程,kill命令也是调用kill函数实现。若不明确信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。调用raise()函数给当前进程发送指定信号。
命令:kill -SIGSEGV 7940  (给某个进程id发送段错误信号也会显示段错误)
      kill -9 7940        (发送9号SIGKILL信号强制终止程序)

4.用户程序中软件条件
闹钟:SIGALRM
读端关闭的管道写数据:SIGPIPE

信号处理方式:
1.忽略:对收到的信号不作物任何回应,有两个型号SIGILL SIGSTOP不能忽略
2.捕捉:收到信号做出相应的回应
3.默认处理:退出进程

#include <signal.h>
信号处理函数(注册)
void (*sighandler_t)(int) signal(int signo,void (*sighandler_t)(int))
收到指定信号,执行指定函数
SIG_IGN则忽略信号,SIG_DFL执行默认信号,函数指针为自定义处理
返回值是执行函数地址


int sigaction(int signo,const struct sigaction *act,struct sigaction * oact)
signo:指定信号的编号
act:指针指向修改后的处理动作,结构体指针。
oact:传出参数,指针指向原来的对signo的处理动作,一般为NULL
struct sigaction
{
 void (*sa_handler)(int); //为SIG_IGN则忽略信号,SIG_DFL执行默认信号,函数指针为自定义处理
 void (*sa_sigaction)(int,siginfo_t *,void *) //新类型信号处理函数
 sigset_t sa_mask; //将要阻塞的信号集合
 int sa_flags;     //信号处理方式掩码(SA_SIGINFO),如用老类型则为0;
 void (*sa_restorer)(void);//保留不要使用
}

信号发送函数
int kill(pid_t pid,int signo)
把指定的信号发送到指定进程
返回值:0 成功
       -1 返回


信号发送
raise(int signo)
自己发给自己类似于kill(getpid(),signo)

暂停进程,直到有一个信号返回,停止结束
pause
pause();


时钟函数
alarm(int seconds)
int seconds 0:取消定时功能,不发送SIGLARM
            >0:指定时间用完,发送SIGLARM


管道文件
FIFO:先进先出,单向操作一个队列结构
管道 尾端:写入数据
     首端:读取数据
管道中容量是有限制,不能一致写数据
管道中数据读取一次则消失,不哪呢过一致读数据
当管道写数据满时,发送管道写满信号,写的进程堵塞
当管道读数据空时,发送管道为空信号,读的进程堵塞

管道分类:
1.匿名管道:
 a.实现具有亲属关系进程通信(fork vfork)
  ls -l | wc -l
 b.堵塞写或读进程
2.命名管道:
  a.任意多个进程之间的通信
    ls -l >>a.txt
    wc -l <<a.txt
  b.不使用O_NONBLOCK:第一次读为空,堵塞
    使用O_NONBLOCK:第一次读为空,不堵塞

 匿名管道

 创建匿名管道
 int pipe(int fd[2]);
 返回值: -1 失败
         0  成功   fd[0]:读 fd[1]:写

写管道
size_t  write(fd[1],buf,strlen(buf))
读管道
size_t  read(fd[0],buf,sizeof(buf)-1);

关闭管道
close(fd[0]);
close[fd[1]];


多个进程使用匿名管道
在父进程中创建一个管道,子进程复制管道标示符,分别在父 子进程中关闭一个标示符

复制文件标示符
int dup(int oldfd)
int dup2(int oldfd,int newfd)
复制成功后,旧的标示符关闭

标准输入流 输出流 错误输出流与管道一起使用,不需要使用write和read函数

pipe -- f[0]是stdin(0)
pipe -- f[1]是stdout(1)

ls -l| wc -l

pipe fork dup2 excel


命名管道
创建一个文件该文件类型为"p",进程分别读或写该文件,文件的内容读完后,内容将不存在

pts/0 :
mkfifo  f1
echo "abcde" > f1

pts/1:
cat f1


创建命名管道文件
int mkfifo(char *pathname,mode_t mode)
char *pathname:文件系统不存在的文件名
mode_t mode:文件模式(rwx)
返回值: 0:成功
        -1:失败

打开管道文件
int  open(char *pathname,int flags)
int flags:O_RDONLY O_WRONLY O_NONBLOCK
不使用O_NONBLOCK:第一次读为空,堵塞
使用O_NONBLOCK:第一次读为空,不堵塞


system v  IPC(消息队列  信号量  内存共享)
Internet Process communication

查看进程通信方式:
ipcs -s|-p|-m


---System V ipc通信---

1.必须要有一个唯一的标示符(key)
2.授予进程访问的权限(与操作文件类似)
3.必须要使用IPC API函数


IPC函数
创建一个key值,区分IPC通信的块
int ftok(char * pathname,int pro_id)
返回值:-1:失败
       成功:key值
char * pathname:已经存在目录或文件,可以访问
int pro_id:指定编号,只能是(0-255)

ftok函数产生key原理:pro_id转换成16进制+pathname目录或文件节点(16进制)


消息队列

消息队列是一个链表队列(先进先出的特点)
一个进程或多个进程可以访问该队列(一个进程读取队列,一个进程写队列)
消息队列函数:

创建消息队列
int msgget(key_t key,int msgflag)
返回值:  成功  返回时消息的标示符(与参数key有关)
         失败  -1;
key_t key:指定IPC唯一标示符,
          值可以有下面的三种情况确定:
   a.指定值((key_t)1234)
   b.IPC_PRIVATE 系统自动创建一个标示符,具有亲属关系进程(fork vfork)
   c.利用ftok函数生成一个key

 发送消息
int msgsnd(int msgid,void * msg,size_t size,int msglfg)
返回值: -1:失败
        0:成功
int msgid:消息队列识别号(msgget返回值)
void * msg:发送的消息,是一个结构体
 struct msgbuf
 {
  long mtype;       /* message type, must be > 0 */
  char mtext[N];    /* message data */
 };

 size_t size:消息的大小sizeof(struct msgbuf ),sizeof(struct msgbuf)-sizeof(long);

int msglfg:控制进程状态(IPC_NOWAIT:立即返回)


消息接收
int smgrcv(int msgid,void * msg,size_t size,int msgtype,int msglfg)
返回值: -1:失败
        0:成功
int msgid:消息队列识别号(msgget返回值)
void * msg:发送的消息,是一个结构体
 struct msgbuf
 {
  long mtype;       /* message type, must be > 0 */
  char mtext[N];    /* message data */
 };

 size_t size:消息的大小sizeof(struct msgbuf ),sizeof(struct msgbuf)-sizeof(long);

int msgtype:指定接收消息编号

int msglfg:控制进程状态(IPC_NOWAIT:立即返回)


消息队列控制函数
int msgctl(int msgid,int cmd,struct msqid_ds *buf)

struct msqid_ds
{
  struct ipc_perm msg_perm;     /* Ownership and permissions */
  time_t          msg_stime;    /* Time of last msgsnd(2) */
  time_t          msg_rtime;    /* Time of last msgrcv(2) */
  time_t          msg_ctime;    /* Time of last change */
  unsigned long   __msg_cbytes; /* Current number of bytes in     queue (nonstandard) */
  msgqnum_t       msg_qnum;     /* Current number of messages in queue */
  msglen_t        msg_qbytes;   /* Maximum number of bytes allowed in queue */
  pid_t           msg_lspid;    /* PID of last msgsnd(2) */
  pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
};


共享内存

先用key创建一块段共享的物理内存页面,再把物理地址映射到本进程中访问。
内存共享是通信速度最快
内存共享不提供任何同步操作
内存共享函数:

创建内存共享函数
int shmget(key_t key,size_t size,int shmflg)

映射内存
int * shmat(int shmid,void * addr,int flag);

释放内存
int shmdt(,void * addr)


信号量
pv操作
多个进程访问或使用同一个资源时,需要用一个特殊的变量来控制进程对资源访问或使用状态

信号量分为两种不同的操作:
1.p操作:一般是用来堵塞进程,进行p操作时,对信号量-1操作,当信号量的值为0时,请求进程等待
2.v操作:一般是用来释放资源,让等待的进程争抢使用,,进行v操作一般是对信号量做+1操作


创建信号量
int semget(key_t key,int nsemvalue,int semflg)
int nsemvalue:信号量数量 >0 有资源可以使用
                         =0  没有资源可以使用

int semop(int semid,struct sembuf* void,int npose)
struct sembuf* void:
struct sembuf
{
   unsigned short sem_num;  信号量数量
   short    sem_op;  信号使用方式(+1 :释放 -1:使用)
   short    sem_flg;  SEM_UNDO:进程结束时释放,如果没有释放,内核释放
   IPC_NOWAIT:进程立即结束,不等待
}

对指定信号控制操作
int  semctl(int semid,int semnum,int cmd)

 

同步:一个过程完整地执行完毕。
异步:一个过程中途中断,执行另一个过程。

 


*************************************************************
               ---线程---

多线程
线程是一个特殊的进程,是属于进程,一个进程可以有多个线程,一个线程只能属于一个进程


进程是最小的单元
所有的线程都共同使用进程的PCB(全局或静态数据 代码)
每个线程都有自己独立的栈空间(每个线程都允许声明属于自己局部变量)

多线程与多进程区别:
1.多线程开销较小,多个线程公用一个PCB代码空间和数据空间,线程之间切换快。
  进程开销较大,每个进程都独自维护一个PCB,有自己的代码段,数据段,堆栈等。切换时间长。
2.线程通信很方便,无需特别的手段进行通信。
  因为线程间可以共享数据(但是数据之间要同步防止多线程同时操作时产生错误)
  进程通信繁琐,只能通过内核或者文件进行通信。
  进程通信方式有信号,管道,ipc,socket等。

---多线程基本操作---
#include<pthread.h>

thread_t :线程的id
linux创建线程

int pthread_create(thread_t * id,thread_attr * attr,void* (*fun)(void*),void* value)

thread_t * id:线程ID
thread_attr * attr:线程属性设置,一般为NULL(默认属性值)
void* (*fun)(void*):线程要执行的代码函数
void* value:线程执行函数时,传给函数的参数值

返回值:0成功
       非0 失败


线程操作成功返回0,失败返回错误号
其他系统函数都是成功返回0,失败返回-1.错误号保存在全局变量errno中

线程等待

int  pthread_join(pthread_t id,void **retvalue)
等待指定线程结束
void **retvalue:指向线程结束时值

堵塞进程,直到指定的线程结束

线程运行结束:
1.代码执行完毕,线程结束
2.pthread_exit  函数结束线程
3.return   结束线程

线程正常结束函数
pthread_exit(int * retvalue);

 


使用线程栈函数
栈异常退出时或pthread_exit退出,自动执行函数

入栈
int  pthread_cleanup_push(void* (*fun)(void*),void* arg)

出栈
int pthread_cleanup_pop(int execute)

出栈是会执行void* (*fun)(void*)


取消指定的线程执行
int pthread_cancel(pthread_t id)
立即结束线程与pthread_exit区别在于后者执行线程清理动作


在线程中获取线程ID
thread_t pthread_self()

进程与线程操作函数和步骤:
进程               线程
fork(vfork)        pthread_create
wait/waitpid       pthread_join
exit/_exit         pthread_exit/pthread_cancel
getpid             pthread_self


---线程同步---

互斥锁
针对多个线程修改同一个资源时,避免资源出现数据修改不同步脏数据

线程互斥操作步骤:
1.初始化互斥锁(pthread_mutex_t 类型变量)
 int pthread_mutex_init(pthread_mutex_t*,pthread_mutex_attr_t )
2.创建多个线程
3.在每个线程中修改进程中资源
4.线程修改资源之前,加锁
  pthread_mutex_lock(pthread_mutex_t*);
5.修改资源
6.线程解锁
  pthread_mutex_unlock(pthread_mutex_t);
7.释放锁
  pthread_mutex_destroy(pthread_mutex_t*)
 

线程同步
利用改变变量的值,控制线程的执行,其它的线程sleep(1);

信号量(pv操作)
p:操作实际为减-1 ,如果发现值为0,其它线程堵塞
v:操作实际为加+1 ,如果发现值大于0,其它线程唤醒

pv操作使用步骤:
1.初始化pv信号量(sem_init)
sem_init(sem_t *sem,int share,int value)
sem_t *sem:pv信号量类型变量
int share:pv信号量是否在多个线程中共享(一般为0 共享)
int value:pv信号量初始值一般为0或1
2.创建线程
3.每一个线程中作pv操作
p:操作:
   sem_wait(sem_t *sem)//信号量值为0 堵塞
   sem_trywait(sem_t *sem)//信号量为0 立即返回
   信号量值必须要-1
v:操作
   sem_post(sem_t * sem)//信号加1

释放pv信号量
   sem_destroy(sem_t *sem)


条件变量
线程通过发送一个信号,唤醒等待进程,进程通过条件判断,堵塞自己,进程在堵塞之前必须要释放资源

线程信号条件变量同步函数:
1.定义一个条件变量,并且初始化:pthread_cond_t
   int pthread_cond_init(pthread_cnod_t*,NULL);
2.堵塞不满足条件的线程:
  int pthread_cond_wait(pthread_cond _t*,pthread_mutex_t *);
  功能:
  1.堵塞当前线程
  2.对公共资源解锁
  3.被唤醒时,对公共资源加锁
3.唤醒因条件变量堵塞线程
  int pthread_cond_signal(pthread_cond _t*);
  功能:
  1.条件满足,发送一个信号,唤醒堵塞的线程,一次只能唤醒一个线程,根据堵塞线程的优先级,堵塞的时间长短
  2.发送信号的线程继续执行
4.广播式唤醒
  int  pthread_cond_broadcast(pthread_cond_t*)
  唤醒所有堵塞的线程
5.释放信号变量
   int pthread_cond_destroy(pthread_cond_t*);

线程池
首先创建一定数量线程,等待执行分配执行任务,任务执行完,线程将不结束,任然等待新的任务分配,直到所有任务执行完毕,将一起结束所有线程,避免反反复复创建线程和结束线程
线程池要求线程处理的任务不能太长
使用线程是如何防止出现大的波峰:
意思是如何防止同时产生大量的线程,方法是使用线程池,线程池具有可以同时提
高调度效率和限制资源使用的好处,线程池中的线程达到最大数时,其他线程就会排队
等候。


LINUX中的锁:
1.spinlock 自旋锁
防止多个内核任务同时进入临界区,有效避免了多处理器并发运行的内核任务竞争共享资源

2.critical section 临界区
保证在某一时刻只有一个线程能够访问数据

3.mutex 互斥锁
保证多个线程对同一个共享资源的互斥访问,与临界区类似,但是比临界区复杂,能在不同应用程序的线程之间实现对资源的安全共享。

4.semaphore 信号量
信号量允许多个线程同时使用共享资源,指出了同时访问共享资源的线程最大数目,创建信号量的同时应该制定允许的最大资源计数和当前可用资源的计数,每增加一个线程对共享资源的访问,当前可用资源计数减1,只要当前可用资源计数大于0,就可以发送信号量信号。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

linux应用编程基础学习笔记,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。