Linux系统编程的学习

1.基本文件I/O

文件操作相关的最基本元素:目录结构、索引节点、文件数据本身

[1]open()

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname:文件路径;flags:打开标志;mode:文件权限(创建文件时,需要设置)

  1. 以只读(O_RDONLY)方式打开文件,文件不存在打开失败,存在则打开成功,文件指针指向开始位置;
  2. 以只写(O_WRONLY)方式打开文件,文件不存在打开失败,存在则打开成功,文件指针指向开始位置,默认不清空源文件内容;
  3. 一般以写方式打开文件,包含创建、只写、清空这三个宏,也就是 O_CREAT|O_RDONLY|O_TRUNC ,并且函数第三个参数为设置权限;
  4. 默认新建文件权限为:mode&(~umask) 怕麻烦可以直接在创建前,将权限掩码改为0----> umask(0);

[2]write()

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

fd :文件标识符 buf:写入数据起始地址 count:数据长度(字节)

返回值:写入字节数 -1:出错

[3]read()

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

fd :文件标识符 buf:读取存储起始地址 count:读取数据长度(字节)

返回值:表示读取字节数 0:到达文件末尾

[4]access()

#include <unistd.h>

int access(const char *pathname, int mode);

mode:F_OK、R_OK……

返回值:文件权限存在则返回0,不存在返回-1;

[5]带参main函数

int main(int argc,char *argv[])

argc:传入参数个数 argv:字符串数组,保存每个字符串起始地址

命令行执行程序时,后面跟上传输的参数!!!!!argv[1]:为传入的第一个参数

argv[0]:是执行程序的那个命令字符串,例如:./a.out

1>实现与Linux系统下cp指令相同的功能(拷贝文件)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,char *argv[]){
	umask(0);
	int fd1 = open(argv[1],O_RDONLY);
	if(fd1 < 0){
		perror("open");
		return 0;
	}
	int fd2 = open(argv[2],O_CREAT|O_WRONLY|O_TRUNC,0777);
	if(fd2 < 0){
		perror("open");
		return 0;
	}
	char c;
	int i = 0;
	char buff[] = "\\/\\/";
	while(read(fd1,&c,sizeof(char))){	
		if(write(fd2,&c,sizeof(char)) > 0){
			printf("[%c]拷贝中\r",buff[i++%4]);
			continue;
		}
		puts("拷贝失败!");
		close(fd1);
		close(fd2);
		return 0;
	}
	puts("拷贝成功!");
	close(fd1);
	close(fd2);
	return 0;
}

2.进程

概念:进程是一个有独立功能的运行处于运行状态的程序,每个进程可以并发执行。

特点:每个进程之间相互独立,一个进程崩溃了,不会影响其他进程的运行;进程之间可以通过内核机制相互通讯。

查看所有进程信息:ps -aux

​ ps -ef

查看进程树:pstree -p

查看动态进程:top

进程目录:/proc中对应进程号文件夹中

[1]fork()

功能:创建子进程

  1. 在父进程中,fork()函数返回值为子进程的进程号pid;
  2. 在子进程中fork()返回值为0:表示子进程创建成功;-1:创建失败;
  3. 子进程会将父进程的内存四区全部copy(包括在缓冲区的内容哦),但只执行fork()函数之后的语句。
  4. 所以我们可以通过,fork()返回值的不同控制父子进程执行不同的程序。

[2]exit()

功能:结束进程
exit(0):表示正常运行结束进程

[3]wait()

#include <stdlib.h>

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *wstatus);

功能:回收子进程资源或僵尸进程(当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源)

注意事项:

  1. 如果没有子进程,wait是一个非阻塞函数

  2. 如果有子进程,wait 是一个阻塞函数, 如果有子进程退出,wait 立马解除阻塞,回收子进程的资源

  3. 返回值的返回 0-255

    WIFEXITED(wstatus):如果子进程是调用exit 正常退出,该宏为真

    WEXITSTATUS(wstatus): 如果上面的宏为真,调用该宏过滤退出返回值

    WIFSIGNALED(wstatus):如果子进程是由于信号退出的,该宏为真

    WTERMSIG(wstatus):如果上面的宏为真,可以调用该宏过滤信号值;例如:子进程是被kill -9结束的,过滤出的信号就是9

[4]system()和exec()

  • system(const char *command):
    • 串行执行新的程序,执行完继续执行剩余原程序;
    • 参数为字符串起始地址或者字符串常量
    • 字符串内容可以是系统命令例如:touch data.txt
  • exec():
    • 执行其他程序,会影响当前程序,所以要手动开一个子进程给它用,才能不影响当前进程的程序;
    • 清空当前程序所有的数据,将要执行的程序数据加载过来, 一旦调用成功,就回不到之前的程序;

​ 几种调用形式:

  1. int execl(const char *path, const char *arg, …);

    int execlp(const char *file, const char *arg, …);

    • 上面两种的第一个参数为:执行的命令程序文件地址,如:"/bin/ls"
    • 后面的参数为每个命令,但最后一个参数要为NULL,如:execl("/bin/ls","ls","-l",NULL);
  2. int execv(const char *path, char *const argv[]);

    int execvp(const char *file, char *const argv[]);

    • 这两个相比较前两个,不同的就是,将后面的所有参数用一个字符串指针数组保存;

      如:char *argv[] = {"ls","-l",NULL};

      execv("/bin/ls",argv);

3.线程

线程创建

[1]pthread_create()

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数说明:

  • pthread : 传出新建线程的标识符
  • attr: 设置线程属性
  • start_routine: 线程入口函数
  • arg: 给新建线程传递参数
  • 返回值:成功返回0,失败返回错误编号

PS: 编译时要手动连接到线程库(gcc XX.c -o xx -lpthread)

pthread = pth;
if(0 != pthread_create(&pth,NULL,pthread_task,NULL)){
	perror("pthread_create");
	return 0;
}
[2]pthread_exit()

功能:线程退出

  1. 在线程入口函数,return和pthread_exit()效果一样,只要在线程函数中调用了pthread_exit(),该线程就结束了
  2. 线程中谨慎使用exit(),这会使整个进程结束
[3]pthread_join()

功能:阻塞,等待回收线程资源

线程同步

[1]互斥锁

是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将不会循环等待,不占用cpu,cpu可以去执行其他线程,cpu利用率高

  1. 定义互斥锁:

    pthread_mutex_t mutex;

  2. 初始化互斥锁

    int pthread_mutex_init(pthread_mutex_ t *restrict mutex,const pthread_mutexattr_t *restrict attr);
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

    ps : 第1 、2步在创建线程之前完成!

  3. 加锁

    int pthread_mutex_lock(pthread_mutex_t *mutex);

  4. 解锁

    int pthread_mutex_unlock(pthread_mutex_t *mutex);

  5. 销毁互斥锁

    int pthread_mutex_destroy(pthread_mutex_t *mutex);

[2]自旋锁

当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。就是说,循环等待时会独占cpu,一旦可以加锁了,就执行线程。

  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 *)
[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()

[4]条件变量
  1. 定义条件变量,和互斥锁,并初始化

    int pthread_cond_destroy(pthread_cond_t *cond); //销毁锁
    int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//定义条件变量+初始化

  2. 生产者

    1. 加锁

    2. 操作资源

    3. 解锁

    4. 发送信号通知阻塞在条件变量上的消费者

      int pthread_cond_broadcast(pthread_cond_t *cond);

  3. 消费者

    1. 加锁

    2. while判断是否有资源

      1. 没有,则调用wait阻塞在条件变量上等待被唤醒
      2. 有,直接消费

      int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

    3. 解锁

4.进程间的通信

1信号

[1]安装信号处理函数

#include <signal.h>

typedef void (*sighandler_t)(int); //

sighandler_t signal(int signum, sighandler_t handler);

signnum:信号值

  • SIG_IGN:忽略信号
  • SIG_DFL:执行默认操作

handler : 函数入口地址

2定时器

[1] alarm()定时器

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

功能:设置闹钟(定时器),只能发送一次信号

[2]setitimer()安装定时器

int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

函数功能: 设置定时器,可以自定义每隔几秒发送信号

参数说明:

  • which : 时间类型 ITIMER_REAL

  • new_value: 设置定时器时间

  • old_value: 返回之前的时间

*struct itimerval {
struct timeval it_interval; / 间隔时间 /
struct timeval it_value; / 开始时间/
};

struct timeval {
time_t tv_sec; / seconds /
suseconds_t tv_usec; / microseconds /
};*

3管道pipe

[1]创建无名管道(有血缘关系的进程间)

#include <unistd.h>

int pipe(int piped[2]);

参数说明:传出参数(读写标识符)

  • pipefd[0]:读标识符
  • pipefd[1]:写标识符
  1. 管道两端都是正常的, 如果管道里没有数据, read函数是阻塞的,
    如果写端断开或者出现异常, read函数解除阻塞,返回值为0
  2. 如果读端异常, 写端继续往管道中写数据,内核会发送SIGPIPE信号,
    该信号的默认操作是程序退出, 所以写端一定要捕捉 SIGPIPE信号
[2]数据封包和解包

用结构体打包包头信息,字符数组data长度为0,表示数据的起始地址也就是包头和数据的分界线

struct pack{
unsigned char ver; //版本号
unsigned char type; //类型
unsigned int len; //长度
char data[0];

};

  1. 数据封包

    1. 申请空间

      p = (struct pack*)malloc(sizeof(struct pack)+数据长度);
      assert(p!= NULL);

    2. 填充数据

      p->ver = 0;
      p->type = 0;
      p->len = 数据长度;
      strcpy(p->data, buff); //根据包头中数据起始地址,将数据放到那块内存中

    3. 写入打包好的数据

      ret = write(fd[1], p, sizeof(struct pack)+数据长度);

  2. 数据解包

    1. 先读包头

      struct pack pk;
      ret = read(fd[0], &pk, sizeof(struct pack));

      if(ret<0)
      {
      perror(“read”);
      break;
      }

      if(0 == ret)// 管道断裂
      {
      printf(“pipe broken…\n”);
      break;
      }

    2. 根据包头信息中的长度读取数据

      ret = read(fd[0], buff, pk.len);
      if(ret == 0)
      {
      printf(“pipe borken…\n”);
      break;
      }

[3]有名管道FIFO

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

  1. 创建管道

    int ret = mkfifo(“./pipe”,0777);
    if(ret < 0){
    if(errno != 17){
    printf(“errno = %d\n”, errno);
    perror(“mkfifo”);
    return -1;
    }
    }

  2. 打开管道文件(读或者写)

    int fd = open(“./pipe”,O_RDONLY);
    if(fd < 0){
    perror(“open”);
    return -1;
    }

4文件锁

多个进程间通过磁盘文件的读写进行通信

#include <unistd.h>

#include <fcntl.h>

int fcntl(int fd, int cmd, … /* arg / );

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) */

};

  1. 函数功能:设置文件锁

  2. 参数说明:

    • fd :文件描述符
    • cmd :命令
    • F_SETLKW :设置文件锁
  3. 写进程文件锁

    int fd = open(“./fcntl.txt”,O_CREAT|O_WRONLY|O_TRUNC,0777);
    if(fd < 0){
    perror(“open”);
    return -1;
    }

    struct flock fk;
    fk.l_type = F_WRLCK;
    fk.l_whence = SEEK_SET;
    fk.l_start = 0;
    fk.l_len = 100;
    fcntl(fd,F_SETLKW,&fk);
    write(fd,“hello”,5);
    fk.l_type = F_ULOCK;
    sleep(10);
    fcntl(fd,F_SETLKW,&fk);
    close(fd);

  4. 读进程文件锁

    int fd = open(“./fcntl.txt”,O_RDONLY);
    if(fd < 0){
    perror(“open”);
    return -1;
    }

    struct flock fk;
    fk.l_type = F_RDLCK;
    fk.l_whence = SEEK_SET;
    fk.l_start = 0;
    fk.l_len = 100;
    char buff[1024];
    fcntl(fd,F_SETLKW,&fk);
    int ret = read(fd,buff,100);
    buff[ret] = ‘\0’;
    printf(“buff:%s\n”,buff);
    fk.l_type = F_ULOCK;
    fcntl(fd,F_SETLKW,&fk);

    close(fd);

5消息队列

查看内核中消息队列:ipcs

  1. 创建消息队列

    int msgget(key_t key, int msgflg);

    • 函数功能: 创建 或者 获取消息队列的标识符

    • 参数说明:

      • key:键值, 一个键值和一个消息队列是一一对应的关系
      • msgflg:设置标志, 一般使用 IPC_CREAT|0777
      • 返回值:
        • 成功返回消息队列的标识符
        • 失败返回-1
  2. 往消息队列发送消息

    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

    • 函数功能: 往消息队列中发送一个消息

      • 参数说明:

        • msqid: 消息队列标识符

        • msgp: 打包消息信息结构体的起始地址

        • msgsz: 消息大小

        • msgflg: 设置阻塞标志, 0 表示阻塞

        • 返回值:成功返回0, 失败返回-1

        • struct msgbuf {
          long mtype; /* message type, must be > 0 /
          char mtext[1]; /
          message data */
          };

  3. 读取消息

    ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

    • 函数功能: 从消息队列中读取数据

    • 参数说明:

      • msqid : 消息队列的标识符

      • msgp: 接收消息数据结构体的起始地址

      • msgtype: 消息类型

        • 如果类型是0, 默认读取第一个消息
        • 如果类型是非零值, 从第一个消息开始查找,读取指定类型的消息
      • msgflg: 设置阻塞标志, 0表示阻塞

      • 返回值: 成功返回读到的实际大小, 失败返回-1

      • struct msgbuf {
        long mtype; /* message type, must be > 0 /
        char mtext[1]; /
        message data */
        };

  4. 移除消息队列

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>

    int msgctl(int msqid, int cmd, struct msqid_ds *buf);

    1. 程序调用

      msgctl(消息队列id,IPC_RMID,NULL);
      
    2. 命令删除:ipcrm -q msqid

6共享内存

  1. 创建共享内存

    int shmget(key_t key, size_t size, int shmflg);

    • 函数功能: 创建共享内存
    • 参数说明:
      • key : 键值
      • size : 内存大小
      • shmflg: 设置标志 IPC_CREAT|0777
      • 返回值:
        • 成功返回共享内存的标识符
        • 失败返回-1
    int shmid = shmget(0x110,1024,IPC_CREAT|0777);
    if(shmid < 0){
        perror("shmget");
        return 0;
    }
    
  2. 系统映射共享内存地址给程序

    void *shmat(int shmid, const void *shmaddr, int shmflg);

    • 函数功能: 将共享内存映射到程序中
    • 参数说明:
      • shmid : 共享内存标识符
      • shmaddr :指定映射地址, 一般使用NULL,由系统指定
      • shmflg: 设置标志 ,一般使用 0
      • 返回值:
        • 成功返回映射的虚拟地址
        • 失败返回 (void*)-1
    void *ps = shmat(shmid,NULL,0);
    
  3. 可以实现一对多,多对多通信,但是要通过文件锁或者信号量实现多个写进程之间的互斥。

7信号量

1.创建信号量

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

int semid  = semget(0x100,个数,IPC_CREAT|0777)

2.信号量操作

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);

  • sops:指向sembuf结构的数组指针
    • unsigned short sem_num:信号量信号量集合中信号量的索引号
    • short sem_op:操作类型的整数整数:加到信号量的值上负数:信号量的值减去绝对值,如果小于零,进程阻塞,直到信号量的值至少等于其绝对值0:导致操作阻塞,直到信号量的值为0才继续。
    • short sem_flg:一个符号位
      • IPC_NOWAIT: 非阻塞操作
      • IPC_UNDO:进程退出的时候自动撤销该次操作
  • nsop s:第二个参数数组的长度
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值