【Linux】Linux 学习笔记2015(系统程序设计)

标签(空格分隔): Linux

Author:atao

程序映像布局

(高)命令行参数,环境变量->栈->共享内存->堆->BSS->DATA->代码段(低)

第一章 linux系统编程概述

一、Linux系统层次结构

1、APP负责系统的功能与行为
2、OS负责与硬件交互
3、结构如下

应用程序(APP)->C库->Linux系统调用接口->Linux操作系统(OS)->硬件接口

二、基本概念

1、进程:一个正在执行的程序实例,有自己的地址空间和执行状态
特点:1)进程是一个实体
2)进程是一个“执行中的程序”
2、线程:是“进程”中某个单一顺序的控制流。
特点:1)一个进程中允许有多个线程执行
2)线程是进程中的一个实体

三、Linux出错处理

1、strerror函数
原型:char *strerror(int errnum);
    int errnum;
    errnum = errno;
    printf("错误提示%s\n", strerror(errnum));
2.perror函数
原型:void perror(const char *str);
    perror("open file failed:");

四、时间函数

1.实例

#include <stdio.h>
#include <time.h>
// clock_t clock(void);
int main(void)
{
	clock_t clk;
	int i;
	for(i = 0; i < 100000000; i++);
	clk = clock();
	printf("clock = %d, secs = %f\n", clk, 
	clk * 1.0 /CLOCKS_PER_SEC);//将毫秒转换为秒
	printf("CLOCKS_PER_SEC = %d\n", CLOCKS_PER_SEC);
	return 0;
}

2.time命令测试程序运行时间
- time 可执行文件
3.strace 命令跟踪一种程序系统调用使用情况
- strace 可执行文件

第二章 系统I/O操作

一、Linux文件

1、普通文件(regular file)
2、目录:包含了其他文件的名字以及指向与这些文件在关信息的指针
3、设备文件(/dev):字符特殊文件、块特殊文件
4、FIFO:用于进程间的通信,有时也将其称为命名管道
5、套接口(socket):用于宿主机间网络通信,也可用于在一台宿主机上的进程间的通信
常用的五个基本函数

open、close、read、write和ioctl

二、文件描述符

1、Linux内核是通过文件描述符区分和引用文件的。
2、通常,一个进程启动时,会打开3个文件

        STDIN_FILENO    //对应的描述符为:0(标准输入)
        STDOUT_FILENO   //对应的描述符为:1(标准输出)
        STDERR_FILENO   //对应的描述符为:2(标准错误处理)

三、文件操作

1、文件处理函数

        – open – 打开或创建一个文件
        – creat – 建立一个空文件 
        – close – 关闭一个文件
        – read – 从文件读入数据
        – write – 向文件写入一个数据
        – lseek – 在文件中移动读写位置 
        – unlink – 删除一个文件 
        – remove – 删除一个文件本身 
        – fcntl – 控制一个文件属性
        – link –创建一个硬链接
        – symlink –创建一个软链接
        – mkdir – 创建一个目录
        – rmdir – 删除一个目录
        – chdir – 切换一个目录
        – getcwd – 把当前目录的名字写到给定的缓冲区buf里
        – opendir – 打开一个目录并建立一个目录流
        – readdir – 指针指向的结构里保存着目录流drip中下一个目录项的有关资料
        – telldir – 记录着一个目录流里的当前位置
        – seekdir – 设置目录流dir的目录项指针位置
        – closedir – 关闭一个目录流并释放与之关联的资源
        – stat – 返回指定文件的详细情况
        – fstat – 根据文件描述符获取状态
        – lstat – 根据文件名来获取状态(常用)

2、模式位(flags)

        – O_WRONLY //以只写方式打开文件
        – O_RDONLY //以只读方式打开文件
        – O_RDWR //以可读写方式打开文件
        – O_CREAT //若欲打开的文件不存在则自动建立该文件
        – O_EXCL //表示可执行,不能与O_CREAT同时设置,否则会出错
        – O_TRUNC //覆盖已存在的文件
        – O_APPEND  //表示追加读写

3、权限位(mode)

        Linux总共用5个八进制数字来表示文件的各种权限,0000
        第一位:表示设置用户ID
        第二位:表示设置组ID
        第三位:表示用户自己的权限位,即所有者权限
        第四位:表示组的权限
        第五位:表示其他人的权限

4、返回值

        1)错误代码,成功打开文件返回文件描述符,否则返回一个负数。
        2)失败的原因可以用全局的errno来指明
            EEXIST  //表示文件已存在
            EACCESS //表示文件
            ENOTDIR //表示不是目录

5、lseek函数中的whence参数

SEEK_SET 从文件头增加
        – SEEK_CUR 从当前位置增加
        – SEEK_END 从文件尾后增加

6、access判断当前里程是否有某个权限

:access(argv[1], F_OK);//检测该文件是否存在
    权限参数:
        R_OK //有读权限
        W_OK //写权限
        X_OK //执行权限 
        F_OK //测试文件是否存在

7、用法

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
// ssize_t write(int fd, const void *buf, size_t count);
int main(void)
{
	int fd = -1;
	char buf[128] = "Hello world";
	ssize_t count;
	char ch = 0;
	// 打开文件
	fd = open("test", O_WRONLY);
	if(-1 == fd)
	{
		perror("open failed");
		return -1;
	}
	// 写文件
	count = write(fd, buf, strlen(buf));
	if(-1 == count)
	{
		perror("write failed");
		goto _out;
	}
	else if(0 == count)
	{
		printf("write nothing\n");
	}
	// 显示写入的内容
	printf("%s\n", buf);
	// 制造文件空洞
	lseek(fd, 10000, SEEK_SET);
	write(fd, &ch, 1);
_out:
	// 关闭文件
	close(fd);
	return 0;
}

8、fcntl文件属性控制函数

    原型:int fcntl(int fd, int cmd, struct flock *lock);
cmd参数:
    F_GETLK //获取文件锁
    F_SETLK //设置文件锁
通常为整个文件加锁的方法是:
    l_whence = SEEK_SET;
    l_start = 0;
    l_len = 0;
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#if 0
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);

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
						(F_GETLK only) */
	...
};
#endif
int set_lock(int fd, short type);
int main(int argc, char **argv)
{
	int fd;
	char *p;
	/* 
	   命令行提供 4 个参数: 
	   1. 命令 
	   2. 文件名 
	   3. l/u(l - 加写锁, u - 不加写锁)
	   4. 要写入文件中的字符串
	 */
	if((argc != 4) || ((argv[2][0] != 'l') && 
		(argv[2][0] != 'u')) || (argv[2][1] != '\0'))
	{
		printf("Usage: %s filename l/u(lock/unlock) string\n", argv[0]);
		return -1;
	}
	fd = open(argv[1], O_WRONLY | O_APPEND);
	if(-1 == fd)
	{
		perror("open failed");
		return -1;
	}
	if(argv[2][0] == 'l')
	{
		// 加写锁
		if(-1 == set_lock(fd, F_WRLCK))
		{
			goto _out;
		}
	}
	// 开始写文件
	p = argv[3];
	while(*p)
	{
		write(fd, p, 1);
		fprintf(stderr, "%c", *p);	// stderr 标准出错, 不带缓冲
		//fprintf(stdout, "%c", *p);	// stdout 标准输出, 行缓冲
		sleep(1);
		p++;
	}
	if(argv[2][0] == 'l')
	{
		// 解锁
		if(-1 == set_lock(fd, F_UNLCK))
		{
			goto _out;
		}
	}
_out:
	close(fd);
	return 0;
}

// 设置文件锁: F_RDLCK 读锁, F_WRLCK 写锁, F_UNLCK 解锁
// 返回 0 成功, -1 失败
int set_lock(int fd, short type)
{
	// lock 必须先清 0, 否则获取锁时会产生无效参数的错误
	struct flock lock = {0};
	int ret = 0;

	// 获取文件锁状态
	if(-1 == fcntl(fd, F_GETLK, &lock))
	{
		perror("fcntl getlk failed");
		ret = -1;
		goto _out;
	}
	// 对整个文件设置锁
	lock.l_type = type;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;
	// 设置文件锁
	while(-1 == fcntl(fd, F_SETLK, &lock));

_out:
	return ret;
}

9.文件描述符变为非阻塞

fcntl( sockfd, F_SETFL, O_NONBLOCK);
// sockfd      是要改变状态的文件描述符.
// F_SETFL     表明要改变文件描述符的状态
// O_NONBLOCK  表示将文件描述符变为非阻塞的.

四、I/O模型

1.FILE结构提供了三各缓冲方式
1)全缓冲:磁盘文件通常是完全缓冲的
2)行缓冲:与终端相关的文件是行缓冲的
3)不缓冲:标准出错是不缓冲的,如stderr
2.高效处理I/O复用的函数:select函数

原型:int select(int numfds, fd_set *readfds, fd_set *writefds, \
    fd_set *exeptfds, struct timeval *timeout);
参数说明:
    readfds:select监视的可读文件句柄集合。 
    writefds: select监视的可写文件句柄集合。 
    exceptfds:select监视的异常文件句柄集合。 
    timeout:本次select()的超时结束时间。

相关的宏解释:

FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。 
FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。 
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。 
FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否可读写,>0表示可读写。 

关于超时的设置:

     struct timeval
     {
        long    tv_sec;     /* seconds */设置秒
        long    tv_usec;    /* microseconds */设置微秒
    };

实用举例:

main() 
{ 
    int sock; 
    FILE *fp; 
    struct fd_set fds; 
    struct timeval timeout;  //用于设置超时时间
    char buffer[256]={0}; //256字节的接收缓冲区 
    //select等待3秒,3秒轮询,要非阻塞就置0
    timeout.tv_sec = 3;
    timeout.tv_usec = 0;
    /* 假定已经建立TCP/UDP连接 */
    sock=socket(...); 
    bind(...); 
    fp=fopen(...); 
    while(1) 
   { 
        FD_ZERO(&fds); 
        //每次循环都要清空集合,否则不能检测描述符变化
        FD_SET(sock,&fds); //添加通信描述符 
        FD_SET(fp,&fds); //添加打开文件描述符 
        maxfdp=sock>fp?sock+1:fp+1;    //描述符最大值加1
        switch(select(maxfdp,&fds,&fds,NULL,&timeout))//select使用 
        { 
            case -1:
                exit(-1);
                break; //select错误,退出程序 
            case 0:
                break; //再次轮询
            default: 
                  if(FD_ISSET(sock,&fds)) 
                  //测试sock是否可读,即是否网络上有数据
                  { 
                    recvfrom(sock,buffer,256,.....);//接受网络数据                     if(FD_ISSET(fp,&fds)) //测试文件是否可写 
                    {
                            fwrite(fp,buffer...);//写入文件 
                    }
                    buffer清空; 
                   }// end if break; 
          }// end switch 
     }//end while 
}//end main

3.高效处理I/O复用的函数:poll函数

原型:int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
返回值说明:
    大于0,代表满足响应事件的文件描述符的个数
    等于0,超时,代表在规定事件内没有事件发生
    小于0,函数使用出错

参数说明:

    struct pollfd
    {
         int fd;        /* 文件描述符 */
         short events;  /* 等待的事件 */
         short revents; /* 实际发生了的事件 */
     };
    nfds //给出要监视的描述符的数目
    timeout //是一个以毫秒为单位的时间
    /*
    如果timeout为0,poll不阻塞
    如果timeout 为-1,poll永远不会超时,直到有事件返回
    */

关于ufds参数的说明:
events //包含所有该文件描述符需要监视的正常事件(读/写).
revents //包含poll返回时该文件描述符已发生的事件(包含异常事件).
events和revents是通过对各种事件的标志符进行逻辑或(|)运算构建的.
事件标志定义:

        POLLIN 数据可读
        POLLOUT 数据可写
        POLLHUP 设备已经断开 
        POLLRPI 高优先级数据可读
        POLLERR 出现异常
        POLLNVAL 文件描述符无效

五、进程编程

1.常见进程控制函数

    - fork //创建一个新进程
    - exec //用一个新进程去执行一个命令行
    - exit //正常退出进程
    - abort //非正常退出一个进程
    - kill //杀死进程或向一个进程发送信号
    - wait //等待子进程结束
    - sleep //将当前进程休眠一段时间
    - getpid //取得当前进程编号
    - getppid //取得父进程编号

2.wait 和 waitpid

    – wait将暂停父进程直到它有一个子进程结束. 
    – waitpid 可以指定等待哪一个子进程结束. 
    – 成功则返回结束的子进程号.
1)函数原型:
        – pid_t wait (int *status) 
        – pid_t waitpid (pid_t pid, int *status, int options);
2)参数与返回值:
        – status返回子进程退出时的状态. 
        – pid > 0 等待指定pid的子进程结束
        – pid = -1 等待任何一个子进程结束
        – options 为0,阻塞等待.
        如果为WNOHANG,不阻塞,没有等到子进程结束返回0.
3)退出时的状态(status)说明:
WIFEXITED(status)
            • 如果子进程正常结束,宏非零 
            • WEXISTSTATUS(status)
             WIFEXITED非零,宏返回子进程退出码低8位
        – WIFSIGNALED(status)
            • 子进程被信号终止,宏非零
            • WTERMSIG(status)
             WIFSIGNALED非零,宏返回终止子进程的信号编码
        – WIFSOPPED(status)
            • 子进程被信号停止,宏非零
            • WSTOPSIG(status)
             WIFSOPPED非零,宏返回停止子进程的信号编码

3.exec函数族:
相关函数:

    execl\execv\execle\execve\execlp\execvp

说明:其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数
4.system函数
格式:system(“控制台命令”);

六、守护进程

1.特性:能在后台运行的进程
2.守护进程的编程要点
1)调用fork(),将让父进程终止

实现:if(pid = fork()) {exit(0);}

2)脱离控制终端,登录会话和进程组
实现:调用setsid()函数使进程成为会话组长
3)禁止进程重新打开控制终端

实现:if(pid = fork()) {exit(0);}//结束第一子进程,创建第二子进程

4)关闭打开的文件描述符

实现:for(i = 0; 关闭打开的文件描述符; i++) {close(i);}

5)改变当前工作目录为根目录

实现:chdir("/");

6)重设文件创建掩模

实现:umask(0);

第三章 进程间通信

一、无名管道(pipe)和命名管道(fifo)
1.无名管道的读写
实现:

/*目的:父进程写,子进程读*/
int pipe_fd[2];
pipe(pipe_fd);
//fork()一个进程
/*这里是子进程
1.关闭写端close(pipe_fd[1]);
2.sleep(2);让父进程先写
3.read(pipe_fd[0], buf, sizeof(buf));子进程读
4.关闭读端close(pipe_fd[0]);
5.exit(0);
*/
/*这里是父亲进程
1.关闭读端close(pipe_fd[0]);
2.write(pipe_fd[1], buf, sizeof(buf));父进程写
3.关闭写端close(pipe_fd[1]);
4.waitpid(pid, NULL, 0);等待子进程结束
5.exit(0);
*/

2.创建有名管道

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

/*创建有名管道*/  
if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL|O_RDWR)<0)&&(errno!=EEXIST)) 
{  
    perror("mkfifo failed:");
    exit(1);
}  
/*
errno != EEXIST //说明不是文件已经存在错误
*/
 /*打开管道*/  
fd=open(FIFO_SERVER,O_WRONLY |O_NONBLOCK,0);  
if(fd==-1)  
{  
    perror("open failed:");  
    exit(1);  
} 
/*
1.设置了O_NONBLOCK 说明要求无法满足时不阻塞,立即出错返回(ENXIO).
2.未设置O_NONBLOCK 说明访问要求无法满足时进程阻塞,如读取FIFO为空时
*/
close(fd); //关闭管道 
unlink(FIFO_SERVER); //删除文件 

第四章 信号与定时器

一、常用命令
1.查看当前系统的所有信号(kill -l)
2.查看当前系统终端的可执行命令(stty -a)

二、signal信号处理机制
1.调用signal()注册一个信号捕捉函数
原型为:

#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

参数说明:
第一个参数(signum):表示要捕捉的信号
第二个参数(handler):是函数指针,表示要对该信号进行捕捉的函数
注意第二个参数:

    1)SIG_IGN   //表示忽略捕捉到的这个信号
    2)SIG_DEF   //采用默认处理
    3)SIGKILL和SIGSTOP信号即不能忽略也不能被捕捉

三、用程序发送信号

1.向其他进程发送信号

1)kill -9 进程号 //在终端杀死指定进程
2)原型:int kill(pid_t pid, int sig);//编程杀进程
 实现:kill(getppid(), SIGTERM);//杀死父亲

2.向自己发送信号

        1)原型:int raise(int sig);
        实现:
            - raise(SIGTERM);//自杀
            - raise(SIGSTOP);//自闭

小技巧:子进程自杀或自闭后,父进程通过wait可以检查子进程的状态。
2)void abort(void);
功能描述:向进程发送SIGABORT信号,默认情况下进程会异常退出
3)int pause(void);
功能描述:将调用进程挂起直到捕捉到信号为止
3.sigqueue信号发送函数

原型:int sigqueue(pid_t pid,int sig, const union sigval value);
功能描述:即可以发送信号,并且能传递附加的信息.
参数说明:
第一个参数pid为接收信号的进程;
第二个参数sig为要发送的信号;
第三个参数value为一整型与指针类型的联合体:

        union sigval 
        {
            int   sival_int;
            void *sival_ptr;
        };

作用:由sigqueue函数发送的信号的第3个参数value的值,可以被进程的信号处理函数的第2个参数info->si_ptr接收到。
4. sigprocmask信号阻塞

原型:
int sigprocmask(int how,const sigset_t *set, sigset_t *oldset);
功能描述:函数sigprocmask是全程阻塞,在sigprocmask中设置了阻塞集合后,被阻塞的信号将不能再被信号处理函数捕捉,直到重新设置阻塞信号集合。
参数说明:

    第一个参数how的值为如下3者之一:
    a:SIG_BLOCK ,将参数2的信号集合添加到进程原有的阻塞信号集合中
    b:SIG_UNBLOCK ,从进程原有的阻塞信号集合移除参数2中包含的信号
    c:SIG_SET,重新设置进程的阻塞信号集为参数2的信号集
    第二个参数set为阻塞信号集
    第三个参数oldset是传出参数,存放进程原有的信号集。

四、计时器与信号

  1. Linux下的两个睡眠函数
    原型:
    unsigned int sleep(unsigned int seconds);
    void usleep(unsigned long usec);

功能描述:
函数sleep让进程睡眠seconds秒,函数usleep让进程睡眠usec毫秒。
注意:
因为sleep在内部是用alarm实现的,所以在程序中最好不要sleep与alarm混用,以免造成混乱。
2.定时器alarm()函数
原型:

    unsigned int alarm(uinsigned int seconds);

功能描述:
实际是一个延时器,好比sleep,两者用其一。
3.定时器setitimer()

原型:int setitimer(int which, const struct itimerval *value, 
struct itimerval *ovalue));
参数说明:
    第一个参数(which):指定了3种类型计时器
        ITIMER_REAL(真实计时器)
        ITIMER_VITUAL(虚拟计时器)
        ITIMER_PROF(实用计时器)
    第二个参数(value):value为一结构体的传入参数,指定该计时器的初始间隔时间和重复间隔时间。
    第三个参数(ovalue):ovalue为一结构体传出参数,用于传出以前的计时器时间设置。
struct itimerval
{
    struct timeval it_interval; /* next value */   //重复间隔
    struct timeval it_value;    /* current value */ //初始间隔 
};

五、信号量

1.概述
1)信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。
2)信号量为正->说明是空闲的
3)信号量为0->说明被占用,测试的线程要进入睡眠队列中,等待被唤醒
2.无名信号量(互斥锁)

sem_t sem_id;//创建无名信号量
sem_init(&sem_id, 0, 1);//初始化无名信号量
sem_wait(&sem_id);//加锁
//读写操作
sem_post(&sem_id);//解锁

3.有名信号量(/dev/shm)
区别:
1)使用时与无名信号量一样,共享sem_wait和sem_post函数.
2)有名信号量使用sem_open代替sem_init.
3)在结束的时候要像关闭文件一样去关闭这个有名信号量.
实现步骤:

    //1.新建有名信号量,设置权限和信号量的值
    shm_fd = sem_open(mysem, ,O_CREAT,0755, 1);
    sem_wait(shm_fd);//加锁
    //读写操作
    sem_post(shm_fd);//解锁
    sem_close(shm_fd);//关闭打开的命名信号量
    sem_unlink(mysem);//销毁有名信号量文件

注意:
编译程序时要带:-lrt或-lpthread
4.sem_getvalue()函数获取当前信号量值:

    原型:int sem_getvalue(sem_t *sem,int *valp);

返回值:返回0表示当前信号量已上锁,否则返回等待该信号量解锁的进程数

六、共享内存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值